Mobx.dart: How we can update existing item in ObservableList.

Created on 6 Nov 2019  路  13Comments  路  Source: mobxjs/mobx.dart

Hi Team,

Facing issue with ObservableList, How we can update existing item in ObservableList.

I try to set using operator []=(int index, T value), but changes not get reflected on Observer. check updateTodo() action.

Below is code snippet.

todo.dart

class Todo {
  String name;

  Todo(this.name);
}

main_store.dart

import 'package:mobx/mobx.dart';
import 'todo.dart';

part 'main_store.g.dart';

class MainStore = _MainStore with _$MainStore;

// The store-class
abstract class _MainStore with Store {
  @observable
  ObservableList<Todo> todos = ObservableList<Todo>();

  @action
  loadTodos() {
    List<Todo> todos = [
      Todo('todo1'),
      Todo('todo2'),
      Todo('todo3'),
    ];
    this.todos.addAll(todos);
  }

  @action
  addTodo() {
    // This Working fine, get reflected on Observer
    Todo todo = Todo('todo' + todos.length.toString());
    this.todos.add(todo);
  }

  @action
  updateTodo(int index) {
    // THIS NEW UPDATED ITEM not get reflected on Observer
    Todo todo = this.todos[index];
    todo.name = 'NEW UPDATED';
    this.todos[index] = todo;
  }
}

page.dart

class Page extends StatelessWidget {
  final MainStore store = GetIt.instance<MainStore>();

  Page({Key key}) : super(key: key) {
    store.loadTodos();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        FlatButton(
          onPressed: () {
            store.addTodo();
          },
          child: Text('Create Todo'),
        ),
        FlatButton(
          onPressed: () {
            store.updateTodo(0);
          },
          child: Text('Update Todo'),
        ),
        Expanded(
          child: Observer(
            builder: (BuildContext context) {
              return ListView.separated(
                itemCount: store.todos.length,
                separatorBuilder: (context, index) {
                  return Divider();
                },
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(store.todos[index].name),
                  );
                },
              );
            },
          ),
        )
      ],
    );
  }
}

Most helpful comment

Thanks @pavanpodila

mobx really awesome, make state management easy and clean code.

It will be good if some one create video/examples of all types of observables and Reactions. initially its difficult to understand concept who don't have background of mobxjs.

mobx :+1: :+1:

All 13 comments

You need to make the name field of Todo an observable, like so:

class Todo {
  @observable
  String name;

  Todo(this.name);
}

Thanks @pavanpodila

Here todo was just sample, in real we have more complex json model which have multiple fields string/int/map/list/object. If we add @observable for each fields will it create performance issue OR @observable come in picture if any Reactions attached?

There is no performance hit to using observables. It's only when you use it in a reaction will there be any computation cost. That too is negligible in most cases

Thanks @pavanpodila

mobx really awesome, make state management easy and clean code.

It will be good if some one create video/examples of all types of observables and Reactions. initially its difficult to understand concept who don't have background of mobxjs.

mobx :+1: :+1:

No worries. Feel free to ask questions.

Hello !

Follow up question on that.

Can we modify todo.name without an action ?

The todos example seems to indicate that we can mutate it directly but I feel like an action should always be required.

Yup you can. Single field changes are automatically wrapped in action 馃

@pavanpodila I'm trying to achieve the same by updating an object variable in an observablelist.
even making name observable not working for me.
I even tried the same example here but the observer isn't being notified with updates only adds.
here is the code:

import 'package:mobx/mobx.dart';

part 'main_store.g.dart';

class MainStore = _MainStore with _$MainStore;

// The store-class
abstract class _MainStore with Store {
  @observable
  ObservableList<Todo> todos = ObservableList<Todo>();

  @action
  loadTodos() {
    List<Todo> todos = [
      Todo('todo1'),
      Todo('todo2'),
      Todo('todo3'),
    ];
    this.todos.addAll(todos);
  }

  @action
  addTodo() {
    // This Working fine, get reflected on Observer
    Todo todo = Todo('todo' + todos.length.toString());
    this.todos.add(todo);
  }

  @action
  updateTodo(int index) {
    // THIS NEW UPDATED ITEM not get reflected on Observer, I even tryed using action to update 
// the second item but nothing reflects unless state is reloaded
    Todo todo = this.todos[index];
    todo.name = 'NEW UPDATED';
    this.todos[index] = todo;
    this.todos[index + 1].changeName("NEW");
  }
}

class Todo {
  @observable
  String name;
  @action
  changeName(String name) {
    this.name = name;
  }

  Todo(this.name);
}

and here is the screen

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:kalimah_dictionary/stores/main/main_store.dart';

class CategoriesScreen extends StatelessWidget {
  final MainStore store = MainStore();

  CategoriesScreen({Key key}) : super(key: key) {
    store.loadTodos();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          FlatButton(
            onPressed: () {
              store.addTodo();
            },
            child: Text('Create Todo'),
          ),
          FlatButton(
            onPressed: () {
              store.updateTodo(0);
            },
            child: Text('Update Todo'),
          ),
          Expanded(
            child: Observer(
              builder: (BuildContext context) {
                return ListView.separated(
                  itemCount: store.todos.length,
                  separatorBuilder: (context, index) {
                    return Divider();
                  },
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(store.todos[index].name),
                    );
                  },
                );
              },
            ),
          )
        ],
      ),
    );
  }
}

What am I missing out there?

You will also need an Observer around ListTile since you are also updating a Todo. Without it, MobX won't know about the changes. The top-level Observer is still needed to keep track of changes at the list level.

An Observer can only track the observables in the immediate execution context of builder. Nested functions inside builder are not visible to Observer.

Also your Todo class is not defined correctly:

It should be:

class Todo = _Todo with _$Todo

abstract class _Todo with Store { /* rest of the definition */ }

Every Store in MobX needs to have this form of declaring Store classes. Thanks to @shyndman for pointing out :-)

I also have the same issue. However, I think the approach given by @pavanpodila is a little bit confusing because we need to make the Todo class a Store, for me I would prefer to treat it as a simple model.

I checked the source of ObservableList and I found this:

 void replaceRange(int start, int end, Iterable<T> newContents) {
    _context.conditionallyRunInAction(() {
      final oldContents = _list.sublist(start, end);
      _list.replaceRange(start, end, newContents);
      _notifyListUpdate(start, newContents, oldContents);
    }, _atom);
  }

 void operator []=(int index, T value) {
    _context.conditionallyRunInAction(() {
      final oldValue = _list[index];

      if (oldValue != value) {
        _list[index] = value;
        _notifyChildUpdate(index, value, oldValue);
      }
    }, _atom);
  }

I noticed that if we assign by []= it will only call _nofityChildUpdate, however, we need _notifyListUpdate if we want to notify observers, which could be implemented by using replaceRange.

I tried but it does not work....馃樋

Put the Observer directly on top of the ListTile, instead of on top of the ListView.builder.

@TarekMedhat did you fix it ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thedejifab picture thedejifab  路  5Comments

dukaric1991 picture dukaric1991  路  5Comments

jasonlewicki picture jasonlewicki  路  3Comments

Feelthewind picture Feelthewind  路  5Comments

jascodes picture jascodes  路  5Comments