Using Scoped Model to maintain app state in flutter - flutter

I need help creating the architecture for my application. I am using Flutter and scoped_model to maintain state.
It's an application that has a login, that displays news in one part of the application, and shows a photo gallery among others. I would like to split this entire thing into separate Models. LoginModel that holds Login state (like username, token, name etc.). NewsModel that contains news retrieved from the API. GalleryModel to hold names of photos etc. I am not sure if this is the best practice to maintain state using scoped_model.
For eg, what If a text box depends on both LoginModel and NewsModel? I am not sure, but I guess it's not possible to retrieve state from two separate models.
Also, the main reason I am maintaining separate Models to hold state is that I don't want the Login part of the app to get refreshed when I bring news. I guess that's how it goes when I put the entire state in a single model.

The scoped_model library is designed to work with multiple models in play at the same time. That's part of the reason that ScopedModel and ScopedModelDescendant are generics and have a type parameter. You can define multiple models near the top of your Widget tree using ScopedModel<LoginModel> and ScopedModel<NewsModel> and then consume those models lower in the tree using ScopedModelDescendant<LoginModel> and ScopedModelDescendant<NewsModel>. The descendants will go looking for the appropriate model based on their type parameter.
I knocked together a quick example. Here are the models:
class ModelA extends Model {
int count = 1;
void inc() {
count++;
notifyListeners();
}
}
class ModelB extends Model {
int count = 1;
void inc() {
count++;
notifyListeners();
}
}
And here's what I'm displaying in the app:
ScopedModel<ModelA>(
model: ModelA(),
child: ScopedModel<ModelB>(
model: ModelB(),
child: ScopedModelDescendant<ModelA>(
builder: (_, __, a) => ScopedModelDescendant<ModelB>(
builder: (_, __, b) {
return Center(
child: Column(
children: [
GestureDetector(
onTap: () => a.inc(),
child: Text(a.count.toString()),
),
SizedBox(height:100.0),
GestureDetector(
onTap: () => b.inc(),
child: Text(b.count.toString()),
),
],
),
);
},
),
),
),
)
It seems to be working just fine. A non-nested approach works as well:
ScopedModel<ModelA>(
model: ModelA(),
child: ScopedModel<ModelB>(
model: ModelB(),
child: Column(
children: [
ScopedModelDescendant<ModelA>(
builder: (_, __, model) => GestureDetector(
onTap: () => model.inc(),
child: Text(model.count.toString()),
),
),
SizedBox(height: 100.0),
ScopedModelDescendant<ModelB>(
builder: (_, __, model) => GestureDetector(
onTap: () => model.inc(),
child: Text(model.count.toString()),
),
),
],
),
),
)

I wanted to give you a simple example on ScopedModel.
pubspec.yaml file must include :-
dependencies:
scoped_model: ^1.0.1
then,
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() => runApp(MyApp()); //main method
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(), //new class MyHomePage
);
}
}
//-----------------------------------CounterModel [used by ScopedModel]
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
//-----------------------------------ends
//-----------------------------------MyHomePage class
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return ScopedModel( // ScopedModel used on top of the widget tree [it is wrapping up scaffold]
model: CounterModel(), // providing the CounterModel class as model
child: Scaffold(
appBar: AppBar(),
body: Container(
child: ScopedModelDescendant<CounterModel>( // ScopedModelDescendant accessing the data through ScopedModel
builder: (context, _, model) => Text("${model._counter}"), // fetching data from model without thinking of managing any state.
),
),
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, _, model) => FloatingActionButton(
onPressed: model.increment, // calling function of model to increment counter
),
),
),
);
}
}
//-----------------------------------ends

Related

Flutter: tinder likes cards without a stack

I am developing a language learning quiz app. The person selects a topic (30 words) and I make a request to the server via the API to get a list of translations and transcriptions.
Because it takes a long time to get the data, I want to only get data for a couple of words. The first card is shown, and the second is already loaded. When the user has worked with the first one (swipe it), the second one appears, and the data for the third one is loaded in parallel.
How can you get data like that? All tinder-cards widgets request a stack, i.e. already prepared data. In my case, this is not allowed.
I'm assuming it should look like this: displaying not a stack but only a single card like
Ui with stack is fine, you need to do a little bit with fetch data logic, using Future, List and setState.
Example: when i swipe, the top data object was swiped and new data object was create at the last (begin fetch and seft setState when done).
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
final int cardSizes = 3;
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
var datas = <AGirl>[];
#override
void initState() {
// generate stack when init
datas = List.generate(widget.cardSizes, (_) => getNewGirl());
}
// function that return a AGirl and register a `setState` when data fetched
AGirl getNewGirl() => AGirl()..fetch().then((_) => setState(() {}));
// function that `swipe` top data object and add new unfetched data object at the last
void swipe() {
datas.removeAt(0);
datas.add(getNewGirl());
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: renderCards(),
),
);
}
List<Widget> renderCards() {
render(AGirl data, int index) {
return Positioned(
left: index * 200,
child: SizedBox(
width: 200,
height: 300,
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('Card $index'),
Text('AGirl id ${data.id}'),
data.hasData
? const Center(child: Text('loaded'))
: const CircularProgressIndicator(),
ElevatedButton(
onPressed: index == 0 ? swipe : null,
child: const Text('swipe'),
),
],
),
),
),
);
}
return datas.reversed
.map((data) => render(data, datas.indexOf(data)))
.toList();
}
}
class AGirl {
final int id;
dynamic data;
AGirl() : id = Random().nextInt(99999);
bool get hasData => data != null;
Future<AGirl> fetch() async {
await Future.delayed(const Duration(milliseconds: 1000));
data = {};
return this;
}
}

Problems with flutter bloc/State Management-How to pass objects between pages?

My internship boss asked me to solve a problem which I am not being able to do so far.
I have been given an ready application. This is basically flattered attendance app.
Now let me tell you clearly where I'm facing the issue.When the user click the student button,It takes the student to a class page,And in the class pledge the user or the student has to select the class by onTap event-One tapped and selected the class.the You, the user, goes to another page where the user will need to tap again to finally go to the check in Page-meaing another OnTap event./
Now my boss wants to get rid of the two OnTap events in the in between.He wants me to directly grow from student button to Student Item Page without losing the contents of the object that was passed. Please note I just show the important codes here scrapping the UI codes because it will become long.Please see if you can understand the codes.
The app uses bloc/statemanagement system.
Student Button-From here starts the flow(only important codes here)
final User user;
SelectUser({this.user});
Expanded(
child: GestureDetector(
onTap: () {
final ClassroomRepository classroomRepository =
ClassroomRepository(
classroomApiClient: ClassroomApiClient(
httpClient: http.Client(),
),
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider(
create: (context) => ClassroomBloc(
classroomRepository: classroomRepository),
child: ClassList(user: this.user),//user object is passed to the ClassList
)));
},
child: Image.asset('assets/logos/studentbutton.png'),
)
The above is passing the user object to ClassList. Below is the full code for ClassList:
class ClassList extends StatelessWidget {
final User user ;
ClassList({this.user});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<ClassroomBloc, ClassroomState>(
builder: (context, state) {
if (state is ClassroomEmpty) {
print('load');
BlocProvider.of<ClassroomBloc>(context)
.add(FetchClassroom(schoolId: this.user.id));
return Center(child: Text('Please Wait'));
} else if (state is ClassroomLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is ClassroomLoaded) {
final classrooms = state.classrooms;
return ListView.builder(
itemCount: classrooms.length,
itemBuilder: (context, position) {
return ClassItem(classroom: classrooms[position]);//GENERATES UI WITH ONTAP EVENT IN THE NEXT PAGE.
},
);
} else {
return Text(
'Something went wrong!',
style: TextStyle(color: Colors.red),
);
}
},
),
));
}
}
Now , it will go to Class ItemPage to perform the first onTap. You can see in the above code(return ClassItem(classroom: classrooms[position]) passing classroom object from repo)
Below is the full code for Class Item Page. HERE , I AM TRYING TO GET RID OF THIS ON TAP BUTTON WITHOUT LOSING THE CONTENT OF THE OBJECT THAT IS PASSED BUT FAILED REPEATEDLY.
import 'package:attendanceapp/blocs/student/blocs.dart';
import 'package:attendanceapp/repositories/student/repositories.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:attendanceapp/models/classroom.dart';
import 'package:attendanceapp/widgets/student/student.dart';
class ClassItem extends StatelessWidget {
final Classroom classroom ;
ClassItem({this.classroom});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
final VisitorRepository studentRepository = VisitorRepository(
visitorApiClient: StudentApiClient(
httpClient: http.Client(),
),
);
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
BlocProvider(
create: (context) => StudentBloc(studentRepository:studentRepository),
child: Student(classroom: classroom),
)
)
);
},//tapping on the card
child: Card(
margin: EdgeInsets.all(20),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(classroom.classroomName,style: TextStyle(fontSize: 30))
)
),
);
}
}
From ClassItem Page , we go to the StudentPage passing ClassRooms as object.//Shows the Appbar and generates UI by returning the next page. Now this is the Page I don’t want to show but only pass the objects.
class Student extends StatelessWidget {
final Classroom classroom ;
Student({this.classroom});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
backgroundColor: Colors.amber[600],
title: Text(classroom.classroomName),
),
body:
Center(
child: BlocBuilder<StudentBloc, StudentState>(
builder: (context, state) {
if (state is StudentEmpty) {
print(this.classroom);
BlocProvider.of<StudentBloc>(context).add(FetchStudent(classId: this.classroom.id));
return Center(child: Text('Please Wait'));
} else if (state is StudentLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is StudentLoaded) {
final students = state.students;
return ListView.builder(
itemCount: students.length,
itemBuilder: (context, position) {
return StudentItem(student:students[position], classroom: classroom,);
},
);
} else {
return Text(
'Something went wrong!',
style: TextStyle(color: Colors.red),
);
}
},
),
));
}
}
Finally, we go from StudentButton Page to StudeDetails Page. Below IS THE SECOND onTap in the studentItem page which I want to get rid. But failed …I want to go From Student Button Page to STUDENT ITEM PAGE without two OnTap evens and Not losing the content of the object. I have been trying for 7 days,but failed. I told my internship boss it is possible because each student has a class but he said do it any how.
Below is the next page we were talking about in the previous page.
I just need to show StudentDetails page starting from studentButton without performing the two onTaps. I have struggling with this for about 7 days, but still couldn’t go from studentButton to studentDetails page skipping the intermittent pages.
class StudentItem extends StatelessWidget {
final Student student;
final Classroom classroom;
StudentItem({this.student, this.classroom});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final StudentAttendanceRepository attendanceRepository =
StudentAttendanceRepository(
studentAttendanceClient: StudentAttendanceClient(
httpClient: http.Client(),
),
);
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: '/studentDetail'),
builder: (context) => BlocProvider(
create: (context) => StudentAttendanceBloc(studentAttendanceRepository: attendanceRepository),
child: StudentDetail(student: student,),
),
),
).then((value){
if(value){
BlocProvider.of<StudentBloc>(context).add(FetchStudent(classId: this.classroom.id));
}
});
},
////UI CODES HERE…NOT PASTED HERE BECAUSE TOO LONG
}
So to access the data on different pages, you can pass the data to your event from page 1 and then in the bloc, you can assign this data to one of the state class. Now on page 2 or page 3, wherever you want to access the data, you can use BlocListener or BlocBuilder and access the data using the state object.

Managing state in Flutter using Provider

I'm trying to implement Provider state management on counter application to understand Provider's functionality better. I have added two buttons with respect to two different text widget. So, now whenever I click any of the two widget both the Text widgets get update and give same value. I want both the widgets independent to each other.
I have used ScopedModel already and got the desire result but now I want to try with provider.
Image Link : https://i.stack.imgur.com/ma3tR.png
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("====Home Page Rebuilt====");
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
//crossAxisAlignment:CrossAxisAlignment.center,
children: [
Consumer<CounterModel>(
builder: (context, value, child) {
return CustomWidget(
number: value.count.toString(),
);
},
),
Consumer<CounterModel>(
builder: (context, value, child) {
return CustomWidget(
number: value.count.toString(),
);
},
),
],
)),
);
}
}
class CustomWidget extends StatelessWidget {
final String number;
const CustomWidget({Key key, this.number}) : super(key: key);
#override
Widget build(BuildContext context) {
print("====Number Page Rebuilt====");
return ButtonBar(
alignment: MainAxisAlignment.center,
children: [
Consumer<CounterModel>(
builder: (context, value, child) {
return Text(
value.count.toString(),
style: Theme.of(context).textTheme.headline3,
);
},
),
FlatButton(
color: Colors.blue,
onPressed: () =>
Provider.of<CounterModel>(context, listen: false).increment(),
child: Text("Click"),
),
],
);
}
}
If you want them independent from each other, then you need to differentiate them somehow. I have a bit of a different style to implement the Provider and it hasn't failed me yet. Here is a complete example.
You should adapt your implementation to something like this:
Define your provider class that extends ChangeNotifier in a CounterProvider.dart file
import 'package:flutter/material.dart';
class CounterProvider extends ChangeNotifier {
/// You can either set an initial value here or use a UserProvider object
/// and call the setter to give it an initial value somewhere in your app, like in main.dart
int _counter = 0; // This will set the initial value of the counter to 0
int get counter => _counter;
set counter(int newValue) {
_counter = newValue;
/// MAKE SURE YOU NOTIFY LISTENERS IN YOUR SETTER
notifyListeners();
}
}
Wrap your app with a Provider Widget like so
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// don't forget to import it here too
import 'package:app/CounterProvider.dart';
void main() {
runApp(
MaterialApp(
initialRoute: '/root',
routes: {
'/root': (context) => MyApp(),
},
title: "Your App Title",
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
/// Makes data available to everything below it in the Widget tree
/// Basically the entire app.
ChangeNotifierProvider<CounterProvider>.value(value: CounterProvider()),
],
child: MaterialApp(
home: HomeScreen(),
),
);
}
}
Access and update data anywhere in the app
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// MAKE SURE TO IMPORT THE CounterProvider.dart file
import 'package:app/CounterProvider.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
CounterProvider counterProvider;
#override
Widget build(BuildContext context) {
/// LISTEN TO THE CHANGES / UPDATES IN THE PROVIDER
counterProvider = Provider.of<CounterProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
//crossAxisAlignment:CrossAxisAlignment.center,
children: [
_showCounterButton(1),
_showCounterButton(2),
],
),
),
);
}
Widget _showCounterButton(int i) {
return ButtonBar(
alignment: MainAxisAlignment.center,
children: [
Text(
i == 1
? counterProvider.counter1.toString()
: counterProvider.counter2.toString(),
style: Theme.of(context).textTheme.headline3,
),
FlatButton(
color: Colors.blue,
onPressed: () {
/// UPDATE DATA IN THE PROVIDER. BECAUSE YOU're USING THE SETTER HERE,
/// THE LISTENERS WILL BE NOTIFIED AND UPDATE ACCORDINGLY
/// you can do this in any other file anywhere in the Widget tree, as long as
/// it it beneath the main.dart file where you defined the MultiProvider
i == 1
? counterProvider.counter1 += 1
: counterProvider.counter2 += 1;
setState(() {});
},
child: Text("Click"),
),
],
);
}
}
If you want, you can change the implementation a bit. If you have multiple counters, for multiple widgets, then just create more variables in the CounterProvider.dart file with separate setters and getters for each counter. Then, to display/update them properly, just use a switch case inside the _showCounterButton() method and inside the onPressed: (){ switch case here, before setState((){}); }.
Hope this helps and gives you a better understanding of how Provider works.

How to properly initialize a Future in Flutter Provider

so I am trying to build up a list in my provider from a Future Call.
So far, I have the following ChangeNotifier class below:
class MainProvider extends ChangeNotifier {
List<dynamic> _list = <dynamic>[];
List<dynamic> get list => _list;
int count = 0;
MainProvider() {
initList();
}
initList() async {
var db = new DatabaseHelper();
addToList(Consumer<MainProvider>(
builder: (_, provider, __) => Text(provider.count.toString())));
await db.readFromDatabase(1).then((result) {
result.forEach((item) {
ModeItem _modelItem= ModeItem.map(item);
addToList(_modelItem);
});
});
}
addToList(Object object) {
_list.add(object);
notifyListeners();
}
addCount() {
count += 1;
notifyListeners();
}
}
However, this is what happens whenever I use the list value:
I can confirm that my initList function is executing properly
The initial content from the list value that is available is the
Text() widget that I firstly inserted through the addToList function, meaning it appears that there is only one item in the list at this point
When I perform Hot Reload, the rest of the contents of the list seems to appear now
Notes:
I use the value of list in a AnimatedList widget, so I am
supposed to show the contents of list
What appears initially is that the content of my list value is only one item
My list value doesn't seem to automatically update during the
execution of my Future call
However, when I try to call the addCount function, it normally
updates the value of count without needing to perform Hot Reload -
this one seems to function properly
It appears that the Future call is not properly updating the
contents of my list value
My actual concern is that on initial loading, my list value doesn't
properly initialize all it's values as intended
Hoping you guys can help me on this one. Thank you.
UPDATE: Below shows how I use the ChangeNotifierClass above
class ParentProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<MainProvider>(
create: (context) => MainProvider(),
),
],
child: ParentWidget(),
);
}
}
class ParentWidget extends StatelessWidget {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
var mainProvider = Provider.of<MainProvider>(context);
buildItem(BuildContext context, int index, Animation animation) {
print('buildItem');
var _object = mainProvider.list[index];
var _widget;
if (_object is Widget) {
_widget = _object;
} else if (_object is ModelItem) {
_widget = Text(_object.unitNumber.toString());
}
return SizeTransition(
key: ValueKey<int>(index),
axis: Axis.vertical,
sizeFactor: animation,
child: InkWell(
onTap: () {
listKey.currentState.removeItem(index,
(context, animation) => buildItem(context, index, animation),
duration: const Duration(milliseconds: 300));
mainProvider.list.removeAt(index);
mainProvider.addCount();
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: _widget,
),
),
),
);
}
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(32.0),
child: mainProvider.list == null
? Container()
: AnimatedList(
key: listKey,
initialItemCount: mainProvider.list.length,
itemBuilder:
(BuildContext context, int index, Animation animation) =>
buildItem(context, index, animation),
),
),
),
);
}
}
You are retrieving your provider from a StatelessWidget. As such, the ChangeNotifier can't trigger your widget to rebuild because there is no state to rebuild. You have to either convert ParentWidget to be a StatefulWidget or you need to get your provider using Consumer instead of Provider.of:
class ParentWidget extends StatelessWidget {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
return Consumer<MainProvider>(
builder: (BuildContext context, MainProvider mainProvider, _) {
...
}
);
}
As an aside, the way you are using provider is to add the MainProvider to its provider and then retrieve it from within its immediate child. If this is the only place you are retrieving the MainProvider, this makes the provider pattern redundant as you can easily just declare it within ParentWidget, or even just get your list of images using a FutureBuilder. Using provider is a good step toward proper state management, but also be careful of over-engineering your app.

Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder

I am using a Selector which rebuilds when a data in Bloc changes. Which woks fine but when the data changes it reloads the whole tree not just the builder inside Selector.
In my case the selector is inside a StreamBuilder. I need this because the stream is connected to API. So inside the stream I am building some widget and One of them is Selector. Selector rebuilds widgets which is depended on the data from the Stream.
Here is My Code. I dont want the Stream to be called again and again. Also the Stream gets called because the build gets called every time selector widget rebuilds.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/data_bloc.dart';
void main() => runApp(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,
),
home: MultiProvider(providers: [
ChangeNotifierProvider<DataBloc>(
create: (_) => DataBloc(),
)
], child: ProviderTest()),
);
}
}
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
StreamBuilder(
stream: Provider.of<DataBloc>(context).getString(),
builder: (_, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + snapshot.data),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}),
],
);
}
return Container();
},
)
],
),
);
}
}
bloc.dart
import 'package:flutter/foundation.dart';
class DataBloc with ChangeNotifier {
int _willChange = 0;
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
Also if I remove the StreamBuilder then the Selector acts like its suppose to. Why does StreamBuilder Rebuilds in this case? Is there anyway to prevent this?
Based on the code that you've shared, you can create a listener to your Stream on your initState that updates a variable that keeps the most recent version of your data, and then use that variable to populate your widgets. This way the Stream will only be subscribed to the first time the Widget loads, and not on rebuilds. I can't test it directly as I don't have your project. But please try it out.
Code example based on your code
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
String _snapshotData;
#override
void initState() {
listenToGetString();
super.initState();
}
void listenToGetString(){
Provider.of<DataBloc>(context).getString().listen((snapshot){
setState(() {
_snapshotData = snapshot.data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + _snapshotData),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}
),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}
),
],
)
],
),
);
}
}
I found the problem after reading this blog post here. I lacked the knowlwdge on how the Provider lib works and how its doing all the magic stuff out of Inherited widgets
The point and quote that solves this problem is. ( A quation from the blog post above)
When a Widget registers itself as a dependency of the Provider’s
InheritedWidget, that widget will be rebuilt each time a variation in
the “provided data” occurs (more precisely when the notifyListeners()
is called or when a StreamProvider’s stream emits new data or when a
FutureProvider’s future completes).
That means the variable that i am changing and the Stream that i am listning to, exists in the Same Bloc! that was the mistake. So when I change the val and call notifyListener() in a single bloc, all things reloads which is the default behaviour.
All I had to do to solve this problem is to make another Bloc and Abstract the Stream to that particular bloc(I think its a Good Practice also). Now the notifyListener() has no effect on the Stream.
data_bloc.dart
class DataBloc with ChangeNotifier {
int _willChange = 0;
String data = "";
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Future<String> getData () async {
return "Data";
}
}
stream_bloc.dart
import 'package:flutter/foundation.dart';
class StreamBloc with ChangeNotifier {
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
And the problem is solved. Now the Stream will only be called if its invoked but not when the variable changes in the data_bloc