'How to change ListTile data with mobx without rebuilding the whole page #Flutter

I am learning the Flutter state management with mobx, and I want to change the context of the dynamic listtile without rebuilding the whole page, I tried observable list from Mobx store but seems not to work here is my samples.

This sample I want to click the item the name change to clicked without rebuilding the screen, when I click nothing happens but when I go back to another page and come back then I found out that the name of the animal changed.

This is my stateless widget

Class AnimalPageList extends StatelessWidget{
 
 @override
 Widget build(BuildContext context){    

var animalStore = context.watch<AnimalStore>();
var animals = animalStore.animalsList;

return Scaffold(
  appBar: AppBar(
    title: Text("Animals Page"),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
       Observer(builder: (_) =>
         ListView.builder(
           scrollDirection: Axis.vertical,
           shrinkWrap: true,
           itemCount: animals.length,
           itemBuilder: (BuildContext context, int index) {
             var pet = animalStore.getAnimal(index);
             //final petName = context.select((ObservableList<Animal> animals)=>animals[index].name);
             return ListTile(
                   key: ValueKey(pet.id),
                   enabled: true,
                   onTap: () async {
                     var rename = animals[index].name +" clicked";
                     animals[index].name = rename;
                   },
                   title: Observer(builder: (_) => Text(pet.name)),

             );
           },
         ),
       ),
      ],
    ),
  ),
);

And here is my model with the store

class Animal {
  final int id;

  @observable
  String name;

  Animal({this.id, this.name});

  factory Animal.fromJson(Map<dynamic, dynamic> json) {
    return new Animal(
      id: json['id'],
      name: json['name'],
    );
  }

  Map<dynamic, dynamic> toJson() {
    final Map<dynamic, dynamic> data = new Map<dynamic, dynamic>();
    data['id'] = this.id;
    data['name'] = this.name;
    return data;
  }
}

class AnimalStore = _AnimalStore with _$AnimalStore;

abstract class _AnimalStore with Store{


  @observable
  ObservableList animalsList = ObservableList<Animal>.of(
    [
      Animal(id: 1, name: 'cat'),
      Animal(id: 2, name: 'dog'),
      Animal(id: 3, name: 'mouse'),
      Animal(id: 4, name: 'horse'),
      Animal(id: 5, name: 'frog'),
    ]
  );

  @action
  getAnimal(int i){
    return animalsList[i];
  }


}

Which way I can arrange, the model, the store and the sreenwidget so that if I click on a button it changes the name as desired whithout requiring to rebuild the whole screen.



Solution 1:[1]

From your code it seems the list size is static and it doesn't change over time, based on this fact you can go for a simple solution as follow:

First, you are observing the whole list, and also the Text widget inside it, over-engineering here.

Just wrap the Observer around the ListTile, so that only a particular ListTile will rebuild upon updating to its observed var. Use UniqueKey() for the key of each element.

Also, you need to reference the vars store inside the Observer. You can call store actions anywhere without the need to be inside an Observer widget.

You need to create an action method to update the name of the animal.

Tips: avoid creating new vars inside a loop or a list that is used only for a temporary hold, the "var rename" inside the builder, is not a good practice.

Normally I create a global instance of the object representing the store and can be re-used by any widget/files on your project.

AnimalStore animalStore = AnimalStore();

The code should be adapted as follow:

//_AnimalStore class

   @action
   setAnimalName(int i, string name){
     animalsList[i].name = name;
   }

//AnimalPageList class

 Class AnimalPageList extends StatelessWidget{
     
     @override
     Widget build(BuildContext context){    
    
    // you dont realy need them
    var animalStore = context.watch<AnimalStore>();
    var animals = animalStore.animalsList;
    
    return Scaffold(
      appBar: AppBar(
        title: Text("Animals Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
           ListView.builder(
               scrollDirection: Axis.vertical,
               shrinkWrap: true,
               itemCount: animalStore.animalsList.length,
               itemBuilder: (BuildContext context, int index) {
                 return Observer(builder: (_) => ListTile(
                       key: ValueKey(pet.id),
                       enabled: true,
                       onTap: () {
                        animalStore.setAnimalName(index, animalStore.getAnimal(index).name +" clicked");
                       },
                       title: Text(animalStore.getAnimal(index)),
    
                 ));
               },
             ),
          ],
        ),
      ),
    );

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 fpatelm