Home > OS >  Flutter: How do I make objects reference each other without a stack overflow error?
Flutter: How do I make objects reference each other without a stack overflow error?

Time:11-28

I want to have a simple set of objects that can reference each other. There are a number of applications for this (i.e.: circular linked lists, many-many-relationships etc.)

I made this quick example in order to demonstrate the issue.

It's a number of "people" objects with a list of people who are their friends. However, when I do this, the building of this code causes a stack overflow. Probably this is because bob calls charlie() and charlie calls bob().

I didn't think this would happen because anna, bob, and charlie are all supposed to just be references to the objects.

If you remove bob from the friend's list of charlie, it works just fine. Below is the boiler-plate code for testing it.

For example: In people.dart

    class People {
      static Person anna = Person(name: "Anna", friends: [bob, charlie]);
      static Person bob = Person(name: "bob", friends: [charlie]);
      static Person charlie = Person(name: "charlie", friends: [bob]);
    
      static List<Person> everyone = [anna, bob, charlie];
    }
    
    class Person {
      String name;
      List<Person> friends;
      Person({required this.name, this.friends = const []});
    }

Here is the boiler plate code. for main.dart

    import 'package:flutter/material.dart';
    
    import 'people.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(title: 'Friends test'),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: Column(
              children: People.everyone
                  .map<Widget>((person) => PersonWidget(person: person))
                  .toList()),
        );
      }
    }
    
    class PersonWidget extends StatelessWidget {
      const PersonWidget({required this.person, super.key});
      final Person person;
    
      @override
      Widget build(BuildContext context) {
        String text = "${person.name}'s friends are: ";
        for (Person friend in person.friends) {
          text  = "${friend.name}, ";
        }
    
        return Text(text);
      }
    }

When bob is not charlies friend:

When bob is not charlies friend

As soon as bob is charlies friend

As soon as bob is charilies friend

Poor charlie can't have any friends: sad face

Final note: Above is just an example. The actual production code has many objects with many properties defined. Similar to how the flutter Colors are defined.(ie: Colors.blue.shade100, etc.). I was hoping to find a simple way to define many objects like this.

CodePudding user response:

I didn't think this would happen because anna, bob, and charlie are all supposed to just be references to the objects.

static variables are initialized lazily, so when you access bob, Person(name: "bob", friends: [charlie]) is executed. That accesses charlie, which similarly executes Person(name: "charlie", friends: [bob]).

However, we haven't finished initializing bob yet. bob has not yet been assigned a reference to an object, because we haven't completed its call to the Person constructor. (And, since Dart is an applicative-order language where arguments are evaluated before invoking functions, we haven't even entered the Person constructor at all yet.) You therefore end up in a circular initialization loop.

You instead should construct the anna, bob, and charlie objects first and then mutate them to refer to the others.

Another approach would be to use closures to defer generating the friend lists so that anna, bob, and charlie can be initialized before references to the other objects are needed. For example:

class People {
  static Person anna = Person(name: "Anna", friends: () => [bob, charlie]);
  static Person bob = Person(name: "bob", friends: () => [charlie]);
  static Person charlie = Person(name: "charlie", friends: () => [bob]);

  static List<Person> everyone = [anna, bob, charlie];
}

typedef ComputeFriends = List<Person> Function();

class Person {
  String name;

  ComputeFriends? _computeFriends;

  List<Person> _friends = const [];

  List<Person> get friends {
    final computeFriends = _computeFriends;
    if (computeFriends != null) {
      _friends = computeFriends();
      _computeFriends = null;
    }
    return _friends;
  }

  Person({required this.name, ComputeFriends? friends})
      : _computeFriends = friends;
}
  • Related