user9715010
user9715010

Reputation:

How to connect multiple models in connector (ScopedModel)

I am trying to add fruit item to the cart, but nothing happens

Once I pressed on the 'add fruit' button nothing happens. It supposes to add fruit items in the cart list. I get an error once trying to access the cart screen by pressing on the cart icon in the app bar after the 'add fruit' button was pressed.

enter image description here

In this way doesn't work properly:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new ScopedModel(
      model: ListModel(),
      child: ScopedModel(
        model: CartModel(),
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'State Management Scoped Model',
          theme: myAppTheme,
          initialRoute: '/',
          routes: {
            '/': (context) => MyList(),
            '/cart': (context) => MyCart(),
          },
        ),
      ),
    );
  }
}

Another way as well:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new ScopedModel<ListModel>(
      model: ListModel(),
      child: ScopedModelDescendant<ListModel>(
        builder: (context, child, model) => ScopedModel<CartModel>(
          model: CartModel(),
        ),
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'State Management Scoped Model',
          theme: myAppTheme,
          initialRoute: '/',
          routes: {
            '/': (context) => MyList(),
            '/cart': (context) => MyCart(),
          },
        ),
      ),
    );
  }
}

enter image description here

With Provider package everything works fine :)

Upvotes: 1

Views: 826

Answers (1)

user12955651
user12955651

Reputation:

Cannot comment yet.

Try adding the MaterialApp as the direct descendant child of ScopedModel and the use of the ScopedModelDescendant when the changes to the Model actually affect the UI.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new ScopedModel<ListModel>(
      model: ListModel(),
      child: MaterialApp(
         debugShowCheckedModeBanner: false,
         title: 'State Management Scoped Model',
         theme: myAppTheme,
         initialRoute: '/',
         routes: {
           '/': (context) => MyList(),
           '/cart': (context) => MyCart(),
         },
       ),
    );
  }
}

and when the property changes lets say in MyCart widget when you list card items

... widget tree ...

child: ScopedModelDescendant<ListModel>(
         builder: (context, child, ScopedModel<CartModel> model){
           List cartItems = model.cartItems;
           List<Widget> cartItemWidgets=[];
           cartItems.forEach((cartItemData){
             cartItemWidgets.add(new CartItemWidget(cartItemData));
           });
           return Column(
             children:cartItemWidgets,
           );
         }
       ),

...widget tree...

Hope this helps.

Note, ScopedModelDescendant changes every time notifyLsteners() is called. Doing so on the entire app would be quite expensive I'd think.

Edit:

forgot to add the rebuildOnChange: true property. And also made a mistake.

... widget tree ...
//__YOUR__MODEL__: model that changes 
child: ScopedModelDescendant<__YOUR__MODEL__>(
         rebuildOnChange: true, // now the widget rebuilds when notifyListeners(); is called inside the __YOUR__MODEL__
         builder: (context, child,__YOUR__MODEL__ model){
           List cartItems = model.cartItems;
           List<Widget> cartItemWidgets=[];
           cartItems.forEach((cartItemData){
             cartItemWidgets.add(new CartItemWidget(cartItemData));
           });
           return Column(
             children:cartItemWidgets,
           );
         }
       ),

...widget tree...

EDIT 2:

From going through the git repository I was able to build an example of what you wanted to do. Here is the link to the GitHub repository. Note: I've initiated an ownership transfer to you. And I'll update the link if you choose to accept. But to also describe the implementation.

You wanted to have two models a ListModel and a CartModel, the list model currently serves as a placeholder for an actual list.

In the CartModel you attached the ListModel.

Whenever you needed the list you got it through the CartModel.

How I changed things?

  1. Split class Fruit into a separate file. // objects.dart

  2. Turned ListModel into abstract ShoppingModel that extends Model

  3. Created a singleton SuperModel that extends Model with ShoppingModel and CartModel. Gave it a private constructor and an instance attribute. There will ever only be one instance of this object. SuperModel is a mixing it is able to access all the public properties of ShoppingModel and CartModel. To avoid editor displayed errors a file was added//analysis_options.yaml that suppresses the error. Note this doesn't affect the program, you can find more information about it on the web.

  4. CartModel extends ShoppingModel, now CartModel has access to all the methods of the ShoppingModel it does not have to store the ListModel. Use ShoppingModel to add attributes that you might have to use across multiple models.

  5. Wrap the app in a ScopeModel, the model attribute is the SuperModel. Since SuperModel is a singleton I used SuperModel.instance, it never has to be instantiated.

  6. Added ScopedModelDescendant everywhere where changes might occur, don't forget the rebuildOnChange: true, property.

I've also given you the owner of the repository.

In general, when using multiple models, use inheritance and mixins if they need to exchange attributes. I always used a top abstract Model descendant class that holds all the data that all of my other Models use. Other models extend this class so they are able to get access to these attributes.

But since we need access to their properties across the app and the abstract Model descendant doesn't know their children, we can create a mixin of all the Model in this case SuperModel. And because we will ever need a single instance make it a Singleton

class SuperModel extend Model with Model1, Model2{

  SuperModel._privateConstructor();

  static final SuperModel _instance = SuperModel._privateConstructor();

  static SuperModel get instance {
    return _instance;
  }

}

now we can pass the SuperModel to the ScopedModel.

To enable error free mixins add:

analyzer:
  strong-mode: true
  language:
    enableSuperMixins: true

to the end of the pubspec.yaml file

and a new root file analysis_options.yaml:

analyzer:
  errors: 
    mixin_inherits_from_not_object: ignore

Now this applies to Visual studio code, I don't know if this is handled any differently for Android Studio

Upvotes: 0

Related Questions