Flutter StreamProvider not pushing data into UI
Wrap directly ListView in a StreamBuilder listening to the interested stream to listen to
StreamBuilder<List<DisplayTaskData>>( stream: getAllTasks(), // provide the correct reference to the stream builder: (context, snapshot) { if (snapshot.data != null) // here your old ListView return ListView.builder( // itemCount: snapshot.data.length, // <- this is better itemCount: contextsnapshot.watch<List<DisplayTaskData>>()?data.length, // here you probably can pass directly the element snapshot.data[index] itemBuilder: (context, index) => _taskListView(context, index), ); else return Text("No Tasks"), })
Because the code you provided is fractional, I can only guess the problem from.
Widget consumer part in TaskView
.
context.watch<List<DisplayTaskData>>()
You should consume the class you put in your provider. From code below, you should use TaskViewModel class the receive notification when data change.
providers: [ ChangeNotifierProvider(create: (_) => taskViewModel), StreamProvider(create:(context) => taskViewModel.getAllTasks())],
getAllTasks() in TaskRepository
From the updated code and the original code, I think you want to do some data modification when stream data come in. The original one is more likely you want but maybe you would like to put formattedTasks
inside the map function.
Stream<List<DisplayTaskData>> getAllTasks(){ var watchAllTasks = AppDatabase().watchAllTasks(); return watchAllTasks.map((tasks) { final formattedTasks = List<DisplayTaskData>(); tasks.forEach((element) { var date = element.dueDate; String dueDateInString; if(date != null){ dueDateInString = "" "${date.year.toString()}-" "${date.month.toString().padLeft(2,'0')}-" "${date.day.toString().padLeft(2,'0')}"; } var displayTaskData = DisplayTaskData(task : element.task, dueDate: dueDateInString); formattedTasks.add(displayTaskData); }); return formattedTasks;}
Other advises I have to mention:
*DON'T reuse an existing ChangeNotifier using the default constructor
You shouldn't use create: (_) => variable
in the main function if it is created.Please check document in provider
final taskViewModel = TaskViewModel();return MultiProvider( providers: [ ChangeNotifierProvider.value(value: taskViewModel), StreamProvider.value(value: taskViewModel.getAllTasks()), ], child: ...
StreamProvider Example
I'm assuming most arriving to this question are wondering why StreamProvider isn't causing rebuilds of UI. So here's a more generic example of a StreamProvider to illustrate a StreamProvider updating the UI with new data from a Stream.
Full code example here on Github.
This is built on the default Counter example when you create a new Flutter project in Android Studio.
It uses Provider and the english_words packages.
In Android Studio, you'll need to edit pubspec.yaml and main.dart.
pubspec.yaml
In pubspec.yaml
, add a couple package dependencies for this example:
provider: ^4.3.2+2 english_words: 3.1.5
e.g.
dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 provider: ^4.3.2+2 english_words: 3.1.5
main.dart
Import Provider & english_words:
import 'package:provider/provider.dart';import 'package:english_words/english_words.dart';
Then replace the _MyHomePageState
State class with the below, plus the RowItem
class below that.
Note:Below the StreamProvider consumer
/context.watch
there is also a StreamBuilder added for comparison. Both the SteamProvider and StreamBuilder will show the same data on the UI.
class _MyHomePageState extends State<MyHomePage> { int _counter = 0; List<RowItem> row; StreamController<List<RowItem>> streamController = StreamController.broadcast(); @override void initState() { super.initState(); row = [RowItem(_counter)]; streamController.add(row); } @override void dispose() { streamController.close(); super.dispose(); } void _incrementCounter() { setState(() { _counter++; row.add(RowItem(_counter)); streamController.add(row); }); } @override Widget build(BuildContext context) { /// STREAM*PROVIDER* HERE /// - wrapped Scaffold in Builder to make consuming widgets "children" of StreamProvider /// instead of siblings. InheritedWidgets like StreamProvider are only useful /// to children widgets who inherit its context from below in the widget tree hierarchy. /// Often you'd wrap your entire MyApp with a Provider, but this keeps this example /// more concise. /// https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple#changenotifierprovider return StreamProvider<List<RowItem>>.value( initialData: row, value: streamController.stream, child: Builder( // <-- Added to make everything below builder: (context) { //<-- this context, children of/inherit from StreamProvider return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Expanded( flex: 1, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ) ], ), ), Expanded( flex: 2, child: Container( color: Colors.lightBlueAccent,/// CONSUMER / CONTEXT.WATCH of STREAM*PROVIDER* HERE child: ListView.builder( itemCount: context.watch<List<RowItem>>().length, itemBuilder: (context, index) { List<RowItem> _row = context.watch<List<RowItem>>(); return ListTile( title: Text( '[${_row[index].num} | ${_row[index].name}]'), ); }, ), ), ), Expanded( flex: 2, child: Container( alignment: Alignment.center, color: Colors.lightGreenAccent,/// STREAM_BUILDER_ for contrast HERE child: StreamBuilder( initialData: row, stream: streamController.stream, builder: (context, snapshot) { if (snapshot.hasData) { List<RowItem> _row = snapshot.data; return ListView.builder( itemCount: _row.length, itemBuilder: (context, index) { return ListTile( title: Text( '[${_row[index].num} | ${_row[index].name}]'), ); }, ); } return Text('Waiting on data...'); }, ), ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }, ), ); }}class RowItem { int num; String name; RowItem(this.num) { name = WordPair.random().asPascalCase; }}
Now stop/restart the project and then you can click the FloatingActionButton to increment the counter and add events to the Stream.
The RowItem objects should show up in both the StreamProvider and the StreamBuilder
For those not using Android Studio the StatefulWidget was this:
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState();}