Access state from parent - flutter - flutter

I try to create a basic notes app to study about flutter and I do not quite understand how to notify my NotesContainer that the button has been pressed. I tried to create a ref to it but the adding function is in the state class that I'm not sure how to reach.
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NotesContainer Notes = new NotesContainer();
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
Notes.add()
},
)
],
),
body: Notes
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return new _NotesContainer();
}
}
class _NotesContainer extends State<NotesContainer>{
final _notes = <NoteData>[new NoteData('title','thing to do'), new NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(new NoteData.noContent(title));
});
}
Widget build(BuildContext context){
return _buildNotesContainer();
}
_buildNotesContainer(){
return new ListView.separated(
itemCount: _notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(_notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
I guess the solution is somehow exposing the _function in the _NotesContainer via the stateful NotesContainer class. I wonder if there is a more elegant solution for this.
Thanks, Or

I think it makes more sense delegating the responsibility of adding a element further up in the widget tree. I modified your code to show how this works.
However, if you eventually get a deep widget tree and the children widgets require the _notes list, then I would recommend that you look into using a Inherited widget and add the _notes list to it, so you can access it without passing the state around too much.
import 'package:flutter/material.dart';
// Note the name change
class NotesPage extends StatefulWidget {
#override
_NotesPageState createState() => _NotesPageState();
}
class _NotesPageState extends State<NotesPage> {
final List<NoteData> _notes = <NoteData>[NoteData('title','thing to do'), NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(NoteData.noContent(title));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
add();
},
)
],
),
body: NotesContainer(notes: _notes)
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatelessWidget{
final List<NoteData> notes;
const NotesContainer({Key key, this.notes}) : super(key: key);
#override
Widget build(BuildContext context){
return ListView.separated(
itemCount: notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
Hope it helps :-)

Related

Update Text with Dissmissble setState

I want to update my Text() value whenever I dismiss an item from the screen .
This is the MainScreen() :
Text.rich(
TextSpan(
text: total().toString() + " DT",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold),
),
The function total() is located in Product Class like this :
class Product {
final int? id;
final String? nameProd;
final String? image;
final double? price;
Product({this.id, this.nameProd, this.image, this.price});
}
List<Product> ListProduitss = [
Product(
price: 100, nameProd: 'Produit1', image: 'assets/images/freedomlogo.png')
];
double total() {
double total = 0;
for (var i = 0; i < ListProduitss.length; i++) {
total += ListProduitss[i].price!;
}
print(total);
return total;
}
I have this in the main screen .
After I remove the item from list , I want to reupdate the Text() because the function is printing a new value in console everytime I dismiss a product :
This is from statefulWidget CartItem() that I render inside MainScreen() :
ListView.builder(
itemCount: ListProduitss.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Dismissible(
key: Key(ListProduitss.toString()),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
total();
// What to add here to update Text() value everytime
});
},
I tried to refresh the main screen but It didn't work .
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
MainScreen();
});
},
One way is to declare a local string variable to use within the text. Then initialise the variable using total() within initState(). Then in setState do the same process.
However, it may be beneficial for you to look into a state management pattern such as BLoC pattern. https://bloclibrary.dev/#/
late String text;
void initState() {
super.initState();
text = Product.total();
}
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
appBar: AppBar(),
body: Column(
children: [
Text(text),
ElevatedButton(child: Text("Update"), onPressed:() => setState(() {
text = Product.total();
}),)
],
)
);
}
I am going to add another example as there was confusion to the above example. Below is an example of updated a text field with the length of the list. It is updated every time an item is removed.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> items = List<int>.generate(100, (int index) => index);
late String text;
#override
void initState() {
text = items.length.toString(); // << this is total;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
Expanded(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (BuildContext context, int index) {
return Dismissible(
background: Container(
color: Colors.green,
),
key: ValueKey<int>(items[index]),
onDismissed: (DismissDirection direction) {
setState(() {
items.removeAt(index);
text = items.length.toString(); // < this is total()
});
},
child: ListTile(
title: Text(
'Item ${items[index]}',
),
),
);
},
),
),
],
);
}
}

Flutter - How to connect void to build widget?

I am trying to get a new historyTile() to be called to the Scaffold() each second. I am unsure how to make the void function connect.
Any advice and feedback is appreciated!
Code:
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
#override
void historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jm().format(now);
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
}
);
}
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (Timer t) => historyTile());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:
Container(
child: SingleChildScrollView(
child: historyTile(); // ERROR HERE
),
),
);
}
}
You can try creating periodic streams with a Stream Builder widget. If not, the simplest method is to put your widget in scaffold and try calling the setState function periodically with a 1-second timer.
In the StreamBuilder example you should change the widget a bit. Sending the parameter you want to update to the widget from outside will add a little more flexibility to you.
return Scaffold(
body: StreamBuilder<String>(
stream: Stream.periodic(const Duration(seconds: 1), (x) {
// Your Action Here
final now = DateTime.now();
return DateFormat.yMMMMd().add_jm().format(now);
}),
builder: (context, snapshot) {
String param = "";
if (snapshot.hasData) param = snapshot.data!;
return _historyTile(txt = param);
}
),
);
Or you could use your widget in Scaffold Body and periodically set the widgets state in timer callback.
class _activityTabState extends State<activityTab> {
String tileTime = "";
...
Timer.periodic(Duration(seconds: 1), () {
setState(() {
final now = DateTime.now();
tileTime = DateFormat.yMMMMd().add_jm().format(now);
});
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: historyTile(tileName);
),
),
);
}
or just
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('$tileTime'),
tileColor: Colors.greenAccent,
),
),
),
);
}
Create your historyTile widget as a custom widget
class HistoryTile extends StatefulWidget {
const HistoryTile({Key? key, required this.txt}) : super(key: key);
final String txt;
#override
State<HistoryTile> createState() => _HistoryTileState();
}
class _HistoryTileState extends State<HistoryTile> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text(widget.txt),
tileColor: Colors.greenAccent,
);
}
}
there is some issues in you ListView.Builder. You do not put itemCount there. And you need to use setState in timer. So codes are below. Please check.
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
String _now;
Timer _everySecond;
#override
historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jms().format(now);
return ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
});
}
void _timer() {
Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
_timer();
});
});
}
#override
void initState() {
super.initState();
_timer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: SingleChildScrollView(
child: historyTile(),
),
),
);
}
}

When I add elements to listview, how to update listview in Flutter?

I am new to flutter and I would like to add element every 5 seconds to my list view. I have list view and I think I have the true adding method. However, I do not know how to update my list view every 5 seconds.
void randomCity(){
List <int> colors = [yellow,green,blue,red,black,white];
List <String> countryNames = ["Gdańsk","Warszawa","Poznań","Białystok","Wrocław","Katowice","Kraków"];
List <String> countryImages = [gdanskPic,warszawaPic,poznanPic,bialystokPic,wroclawPic,katowicePic,krakowPic];
Random random = new Random();
DateTime now = new DateTime.now();
Future.delayed(Duration(seconds: 5), (){
setState(() {
int randomCity = random.nextInt(countryNames.length);
int randomColor = random.nextInt(colors.length);
countrylist.add(Country(
countryNames[randomCity], countryImages[randomCity],
colors[randomColor], now.toString()));
});
});
}
In this code I am adding new element to my list view.
randomCity();
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
backgroundColor: Colors.grey[100],
elevation: 0.0,
title: Text(
"Random City App",
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.add,
color: Colors.black,
size: 32,
),
onPressed: () {})
],
),
body: ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => CountryDetails(countryName: countrylist[index].name,
appBarColor: countrylist[index].color, date: countrylist[index].date, image: countrylist[index].image,))
);
},
title: Text(countrylist[index].name + " ${countrylist[index].date}"),
tileColor: Color(countrylist[index].color),
),
);
},
));
}
And this is my ListView.Builder.
You have to convert your widget into StatefulWidget and then rebuild it with setState (more info on ways to manage state https://flutter.dev/docs/development/data-and-backend/state-mgmt/options)
class MyApp extends StatelessWidget { // your main widget
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget { // create new StatefulWidget widget
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<Country> countrylist = []; // mover other variables in here
...
void randomCity(){
...
setState(() {}); // this will rebuild your widget again and again
}
#override
Widget build(BuildContext context) {
Future.delayed(Duration(seconds: 5), (){
randomCity();
});
return ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(countrylist[index]),
),
);
},
);
}
}
You have to tell the ListView to rebuild which you can do with the setState method (if you are using a StefulWidget). Also, I would use Timer instead of Future.delayed for periodic updates. Here would be a simplified example of your usecase:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
final countryNames = ['Germany', 'Croatia', 'Turkey', 'USA'];
List<String> countryList = [];
#override
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
int randomCity = Random().nextInt(countryNames.length);
countryList.add(countryNames[randomCity]);
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Updater'),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Card(
child: Text(countryList[index]),
);
},
itemCount: countryList.length,
),
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}

Flutter AnimatedList with Provider Pattern

I have model which implements ChangeNotifier
class DataModel with ChangeNotifier{
List<Data> data = List<Data>();
void addData(Data data){
data.add(data);
notifyListeners();
}
}
and a ListView which listens to those changes:
class DataListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<DataModel>(
builder: (context, model, child) {
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index) {
return Text(model.data[index].value);
},
);
},
);
}
}
so far so good, when an item is added to the list in the model, the change notification triggers a rebuild of the Listview and I see the new data. But I cant wrap my head around using this with a AnimatedList instead of a ListView. Preferably id like to keep my model as it is, seeing as the animation is a concern of the ui and not of my logic.
The changenotifier always gives me a uptodate version of my data, but what i really need is a "item added" or "item removed" notification.
Is there a best practice way of doing this?
This is the result of my trial.
It's a riverpod version, but I think it's the same for providers.
There are two points.
Initialize the state in the parent widget of the widget that uses
AnimatedList.
Add / delete AnimatedList and add / delete states asynchronously by using async.
main.dart
import 'package:animatedlist_riverpod_sample/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
class Home extends HookWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final todoList = useProvider(todoListProvider.state);
return Scaffold(appBar: AppBar(title: Text('Todo[${todoList.length}]')), body: TodoListView());
}
}
class TodoListView extends HookWidget {
TodoListView({Key key}) : super(key: key);
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final todoList = useProvider(todoListProvider.state);
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: todoList.length,
itemBuilder: (context, index, animation) =>
_buildItem(todoList[index], animation, index, context),
);
}
Slidable _buildItem(Todo todo, Animation<double> animation, int index, BuildContext context) {
return Slidable(
actionPane: SlidableDrawerActionPane(),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: ListTile(title: Text(todo.description), subtitle: Text(todo.id), onTap: () => {})),
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () {
_listKey.currentState.removeItem(
index, (context, animation) => _buildItem(todo, animation, index, context),
duration: Duration(milliseconds: 200));
_removeItem(context, todo);
},
),
],
);
}
void _removeItem(BuildContext context, Todo todo) async {
await Future.delayed(
Duration(milliseconds: 200), () => context.read(todoListProvider).remove(todo));
}
}
provider.dart
import 'package:hooks_riverpod/all.dart';
final todoListProvider = StateNotifierProvider<TodoList>((ref) {
return TodoList([
Todo(id: '0', description: 'Todo1'),
Todo(id: '1', description: 'Todo2'),
Todo(id: '2', description: 'Todo3'),
]);
});
class Todo {
Todo({
this.id,
this.description,
});
final String id;
final String description;
}
class TodoList extends StateNotifier<List<Todo>> {
TodoList([List<Todo> initialTodos]) : super(initialTodos ?? []);
void add(String description) {
state = [
...state,
Todo(description: description),
];
}
void remove(Todo target) {
state = state.where((todo) => todo.id != target.id).toList();
}
}
sample repository is here.
I recently started to learn Flutter and was surprised to find that this topic isn't covered properly anywhere. I came up with two approaches which I called Basic and Advanced. Let's start from Basic. It's named like that because Provider is called within the same widget where AnimatedList is built.
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
int get length => _list.length;
operator [](index) => _list[index];
int add() {
final int index = length;
_list.add('$index');
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
notifyListeners();
return user;
}
}
class BasicApp extends StatelessWidget {
const BasicApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(create: (_) => Users(), child: AnimatedListDemo()));
}
}
class AnimatedListDemo extends StatelessWidget {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
AnimatedListDemo({Key? key}) : super(key: key);
void addUser(Users users) {
final int index = users.add();
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
}
void deleteUser(Users users, int index) {
String user = users.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(users, user));
},
duration: const Duration(seconds: 1),
);
}
Widget _buildItem(Users users, String user, [int? removeIndex]) {
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteUser(users, removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Basic AnimatedList Provider Demo'),
),
body: AnimatedList(
key: _listKey,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(users, users[index], index),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => addUser(users),
tooltip: 'Add an item',
child: const Icon(Icons.add),
),
);
}
}
Advanced approach differs in that it encapsulates AnimatedListState. I took this idea from Flutter's AnimatedList docs.
typedef RemovedItemBuilder = Widget Function(
String user, BuildContext context, Animation<double> animation);
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
final RemovedItemBuilder _removedItemBuilder;
Users(this._removedItemBuilder);
int get length => _list.length;
operator [](index) => _list[index];
GlobalKey<AnimatedListState> get listKey => _listKey;
int add() {
final int index = length;
_list.add('$index');
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
_listKey.currentState!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return _removedItemBuilder(user, context, animation);
},
duration: const Duration(seconds: 1),
);
notifyListeners();
return user;
}
}
class AdvancedApp extends StatelessWidget {
const AdvancedApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: AnimatedListDemo());
}
}
class AnimatedListDemo extends StatelessWidget {
const AnimatedListDemo({Key? key}) : super(key: key);
Widget _buildItem(BuildContext context, String user, [int? removeIndex]) {
Users users = Provider.of<Users>(context, listen: false);
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => users.removeAt(removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (_) => Users((user, context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(context, user));
}), child: Scaffold(
appBar: AppBar(
title: const Text('Advanced AnimatedList Provider Demo'),
),
body: Consumer<Users>(builder: (BuildContext context, Users users, _){
return AnimatedList(
key: users.listKey,
shrinkWrap: true,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(context, users[index], index),
);
},
);
}),
floatingActionButton: const AddButtonSeparateWidget(),
));
}
}
class AddButtonSeparateWidget extends StatelessWidget {
const AddButtonSeparateWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return FloatingActionButton(
onPressed: users.add,
tooltip: 'Add an item',
child: const Icon(Icons.add),
);
}
}
All code is published on Github. Now I want to elaborate a bit on your proposition of having "item added" or "item removed" notifications. From what I understand it goes against Flutter's philosophy where widget is a UI config. When a widget's state changes, Flutter diffs against its previous state and magically applies the diff to UI. That's why I didn't use "item added", "item removed" notifications in my implementations. However I think it should be possible to do because I saw a similar approach in Firestore subscription to document changes although for now I can't figure how to implement the same with Provider. Provider's documentation is kind of poor. After a careful reading I can't say how to implement partial updates with Provider. May be ProxyProvider with its update could help or may be ListenableProvider. Let me know if you could find the solution to your proposition.

Proper page navigation

I am trying to navigate to a page called contactView. I have made a list of contacts and I wait to navogate to a contact when I click on there name. This is what I have so far. I am stuck trying to get the navigation to work. Any help would be great.
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return new ListView.builder(
padding: new EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return new _ContactListItem(_contacts[index]);
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact())
);
},
itemCount: _contacts.length,
);
}
}
Here are few things that I can immediately point out (Problems):
onPressed is not available on ListView.builder() , you may check
here:
https://docs.flutter.io/flutter/widgets/ListView/ListView.builder.html
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact()) this won't execute because it is after return
Suggestions:
You might need to wrap your _ContactListItem() inside a
GestureDetector and implement an onTap callback
Sample Code:
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
//TODO: Insert your navigation logic here
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
ContactView(_contacts[index])));
},
child: _ContactListItem(_contacts[index]),
);
},
itemCount: _contacts.length,
);
}
}
Another option could be to change the implementation of
_ContactListItem() and may be use a ListTile and implement an onTap in ListTile, you can find it here: https://docs.flutter.io/flutter/material/ListTile-class.html
You may also try to implement named routes, here is a tutorial for
that https://flutter.io/cookbook/networking/named-routes/
I hope this was helpful in someway, let me know if I misinterpreted the question.
See if the below is what you're looking for.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Contact Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Contact Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _contacts = [
Contact(name: 'John'),
Contact(name: 'Mary'),
Contact(name: 'Suzy')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
title: const Text(
'Contact Demo',
style: const TextStyle(color: Colors.white),
),
),
body: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Contact #$index'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) =>
ContactView(contact: _contacts[index]),
));
},
);
},
),
);
}
}
class Contact {
Contact({this.name});
final String name;
}
class ContactView extends StatelessWidget {
ContactView({this.contact});
final Contact contact;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(contact.name),
),
body: Center(
child: Text(contact.name),
),
);
}
}