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),
);
},
);
},
),
)
],
);
}
}
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 ?
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: