How to create a dependency for ChangeNotifierProvider and make it wait to complete? - flutter

I have ChangeNotifierProvider object that uses data stored sqflite asset database which need to be loaded at the beginning as future. The problem is that ChangeNotifierProvider doesn't wait for future operation to complete. I tried to add a mechanism to make ChangeNotifierProvider wait but couldn't succeed. (tried FutureBuilder, FutureProvider, using all together etc...)
Note : FutureProvider solves waiting problem but it doesn't listen the object as ChangeNotifierProvider does. When I use them in multiprovider I had two different object instances...
All solutions that I found in StackOverflow or other sites don't give a general solution or approach for this particular problem. (or I couldn't find) I believe there must be a very basic solution/approach and decided to ask for your help. How can I implement a future to this code or how can I make ChangeNotifierProvider wait for future?
Here is my summary code;
class DataSource with ChangeNotifier {
int _myId;
List _myList;
int get myId => _myId;
List get myList => _myList;
void setMyId(int changeMyId) {
_myId = changeMyId;
notifyListeners();
}
.... same setter code for myList object.
DataSource(){initDatabase();}
Future<bool> initDatabase() {
.... fetching data from asset database. (this code works properly)
return true;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<DataSource>(
create: (context) => DataSource(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
Following code and widgets has this code part (it works fine)
return Consumer<DataSource>(
builder: (context, myDataSource, child) {.......

There are multiple ways that you can achieve. The main point of it is that you should stick to reactive principle rather than trying to await the change. Say for example, you could change the state of boolean value inside the DataSource class when the ajax request changes
class DataSource extends ChangeNotifier{
bool isDone = false;
Future<bool> initDatabase(){
//Do Whatever
isDone = true;
notifyListeners();
}
}
Then you could listen to this change in the build method like so
Widget build(BuildContext ctx){
bool isDone = Provider.of<DataSource>(context).isDone;
if(isDone){
// render result
}else{
// maybe render loading
}
}

Related

Best practice for passing param Riverpod's providers only once

I just want to build a Provider which asks params only one and inits correctly.
Since I am just passing params only once, I don't prefer to use .family methods.
I prefer to use .autoDispose which considered the better way.
Here my tryouts:
I tried to make my own .init() method. But it's disposing as soon as method called if it's .autodispose() and the widget not started to listen my provider yet (that's expected). Therefore I couldn't consider a safe way to do that.
I tried .overrideWith() method in a widget basis. But it's neither worked nor I am sure that it's best practice.
Here is my simple code:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
final myString = 'Hey';
#override
Widget build(BuildContext context, WidgetRef ref) {
//Not worked
ProviderContainer(
overrides: [messageProvider.overrideWith(() => ViewModel(myString))]);
return Scaffold(
body: ProviderScope(
//Not worked either
overrides: [messageProvider.overrideWith(() => ViewModel(myString))],
child: Center(
//I just didn't use .when to shorter code
child: Text(ref.watch(messageProvider).value!.counter.toString()),
),
),
);
}
}
final messageProvider = AsyncNotifierProvider.autoDispose<ViewModel, Model>(
() => throw UnimplementedError());
class ViewModel extends AutoDisposeAsyncNotifier<Model> {
final String param;
ViewModel(this.param);
#override
FutureOr<Model> build() {
//Make some fetch with param, (only once!)
return Model(param.length);
}
}
When I run that. It gives UnimplementedError
Waiting your suggestions & fixes. Thanks in advance!
Expected:
Works properly.
#riverpod
ViewModel myViewModel(MyViewModelRef ref, String param){
return ViewModel(param);
}
This is autoDispose by default in Riverpod 2. If you don't want to auto dispose you can use #Riverpod(keepalive:true) instead of #riverpod
If you don't want to pass the param to the provider, you can eliminate it and hardcode the value to the ViewModel, but at that point, if there are no other dependencies, might as well make it a public final variable in some file, since it looks like this is a singleton that never changes so it is questionable what you'd achieve by making it a Riverpod provider.

How can I use flutter provider to get data from Firestore?

I used here Future Provider to get data from firestore But it's not allowing me to set the initial Data to null??? It ask me to input a type of . How can I use future Provider to get data from firestore.
class MyHomePage extends StatelessWidget {
final _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
return FutureProvider<DocumentSnapshot>(create: (_)async{
return FirebaseFirestore.instance.collection("User").doc("xxxx").get();}, initialData: ,child: Welcome,)
}}
Widget Welcome (BuildContext context){
final document = Provider.of<DocumentSnapshot>(context).data;
if(document==null){
return Container(
child: Text("Loading"),);}
}
Instead of creating a FutureProvider of DocumentSnaphot, a good solution would be to create a class that wraps the DocumentSnapshot. For example:
class MyClass {
MyClass(){}
Future<DocumentSnapshot> getData() async {
return await FirebaseFirestore.instance.collection("User").doc("xxxx").get();
}
}
And in the provider declaration you might set something like
...
Provider(create: (_) => MyClass())
...
This wouldn't require you to set the initial data.
However, for your case and what it seems that you are trying to do, using an StreamProvider would be better.
For more examples and details on this, I recommend checking out the following websites. You'll find more useful information there.
https://firebase.flutter.dev/docs/firestore/usage
https://pub.dev/documentation/cloud_firestore/latest/

BlocBuilder not updating after cubit emit

UPDATE After finding the onChange override method it appears that the updated state is not being emitted #confused
UPDATE 2 Further debugging revealed that the StreamController appears to be closed when the updated state attempts to emit.
For some reason 1 of my BlocBuilders in my app refuses to redraw after a Cubit emit and for the life of me I cannot figure out why, there are no errors when running or debugging, the state data is being updated and passed into the emit.
Currently, it is a hook widget, but I have tried making it Stateless, Stateful, calling the cubit method in an effect, on context.bloc.
I have tried making it a consumer, nothing makes it into the listen, even tried messing with listenWhen and buildWhen and nothing is giving any indication as to why this is not building.
It renders the loading state and that is where it ends.
Widget:
class LandingView extends HookWidget {
#override
Widget build(BuildContext context) {
final hasError = useState<bool>(false);
return Scaffold(
key: const Key(LANDING_VIEW_KEY),
body: BlocProvider<CoreCubit>(
create: (_) => sl<CoreCubit>()..fetchDomainOptions(),
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
switch (state.status) {
case CoreStatus.loaded:
return LandingViewLoaded();
case CoreStatus.error:
return LandingViewError();
case CoreStatus.loading:
default:
return AppIcon();
}
},
),
),
);
}
}
Cubit method:
Future<void> fetchDomainOptions() async {
final inputEither = await getDomainOptions();
return inputEither.fold(
_handleFailure,
(options) {
emit(state.copyWith(
domainOptions: options,
status: CoreStatus.loaded,
));
},
);
}
I have a few other widgets that work off freezed data classes and work of the same status key logic without any issues, I even went as far as trying to add a lastUpdated timestamp key onto it to make even more data change, but in the initial state domainOptions is null and status is CoreStatus.loading, which should already be enough to trigger a UI update.
TIA
I haven't figured out yet why exactly this happens but I believe we're dealing with some kind of race condition here.
Also, I don't know the proper solution to work around that but I have found that delaying calling emit() for a short amount of time prevents this issue from occurring.
void load() async {
try {
emit(LoadingState());
final vats = await provider.all();
await Future<void>.delayed(const Duration(milliseconds: 50));
emit(LoadedState(vats));
} catch (e) {
print(e.toString());
emit(ErrorState());
}
}
Add await Future<void>.delayed(const Duration(milliseconds: [whatever is needed])); before emitting your new state.
I came to this conclusion after reading about this issue on cubit's GitHub: Emitted state not received by CubitBuilder
i resolved my problem, i was using getIt.
i aded the parameter bloc.
my problem was not recibing the events (emit) of the cubit (bloc)
child: BlocBuilder<CoreCubit, CoreState>(
bloc: getIt<CoreCubit>(), // <- this
builder: (context, state) {
},
),

flutter: inter-bloc communication, passing data events between different blocs

I haven't found much about inter-bloc communication, so I came up with an own, simple solution that might be helpful to others.
My problem was: for one screen I use 2 blocs for different information clusters, one of them also re-used on another screen. While passing data is well documented, I had issues with figuring out how to pass events or trigger states to/of the other bloc.
There are probably much better solutions, but for other flutter or bloc beginners like me it might be helpful. It is fairly simple and the logic is easy to follow.
If you inject Bloc A as dependency to Bloc B (looked simple to me and I do not need further Blocs), I can get/set values in Bloc A from Bloc B (not vice versa). If I want to get data back to Bloc A, or if I just want the Bloc A build to reload, I can trigger events in the BlocBuilder of B to pass the information.
// ========= BLOC FILE ===========
class BlocA extends BlocAEvent, BlocAState> {
int myAVar = 1;
}
class BlocB extends BlocBEvent, BlocBState> {
BlocB({#required this.blocA}) : super(BInitial());
final BlockA blockA;
// passing data back and forth is straight forward
final myBVar = blockA.myAVar + 1;
blockA.myAVar = myBVar;
#override
Stream<BState> mapEventToState(BEvent event) async* {
if (event is BInitRequested) {
// trigger state change of Bloc B and request also reload of Bloc A with passed argument
yield LgSubjectShowSingle(blocAReloadTrigger: true);
}
}
}
// ========= UI FILE ===========
class MyPage extends StatelessWidget {
MyPage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
// inject dependency of page on both Blocs: A & B
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) =>
BlocA().add(BlocAInit()),
),
BlocProvider<BlocB>(
create: (BuildContext context) =>
BlocB(BlocA: BlocProvider.of<BlocA>(
context),).add(BInitRequested()),
),
],
child: BlocBuilder<BlocB, BState>(
builder: (context, state) {
if (state is BShowData) {
// If a reload of Bloc A is requested (we are building for Bloc B, here) this will trigger an event for state change of Bloc A
if (state.triggerStmntReload) {
BlocProvider.of<BlocA>(context).add(AReloadRequested());
};
return Text("abc");
}
}
)
);
}
}

Where do I put commands that run once at beginning?

I really miss C programming. Flutter is quite confusing.
Here is the problem:
We have a function within the Stateful class Home. That creates a page called myMenu.
class Home extends StatefulWidget {
myMenu createState() => myMenu();
}
class myMenu extends State<Home>
{
void myProblemFunction(String stringItem) async {
final db = await myDatabaseClass.instance.database;
...
}
Whenever myProblemFunction runs, it will instantiate a new database instance.
I just want to put this command once (i.e.:
final db = await myDatabaseClass.instance.database
) anywhere in my document (this is my main.dart file (by the way). Now, I could put it in the initState() - sure.. but that's a headache, because a) I would have to make it async, and I feel that will cause me problems, and b) declaring final db... (etc) in the initState() will not make the db variable visible to my functions.
What do I do?
what are the elements in myMenu Class that are triggered? Should I put this function inside the widget build method? But surely if the widget is refreshed, then this function will also be called (again) - I just want to call it once.
Thanks
you can use FutureBuilder
example:
FutureBuilder(
future: myDatabaseClass.instance.database,
builder: (BuildContext context,AsycnSnapshot snapshot){
//your code here
},
)
this will only load the Future once when the widget is built.
put your code that you want to run only once in initState():
#override
void initState() {
super.initState();
// TODO code you want to run only once
}