Trouble rebuilding a StreamProvider to update its current data - flutter

so I have aConsumer<NotificationProvider> and in its builder function a StreamProvider<List<Item>>.
Please note that the latter widget builds perfectly on its initial load.
NotificationProvider contains a list of notifications from Firebase Cloud Messaging, ergo when I receive a notification, I push something into the class' Listand then callnotifyListeners()`.
Please also note that my NotificationProvider is doing a good job because I have a counter at my AppBar and it's updating whenever I receive one.
Now on to the meat and potato.
I'm trying to rebuild the StreamProvider whenever NotificationProvider.addAlert() is called. But somehow it's not working?
I also added updateShouldNotify: (prev, next) => true, but it didn't help one bit.
Please help. Thanks!
I also added updateShouldNotify: (prev, next) => true, on the StreamProvider properties but it didn't help one bit.
return Consumer<NotificationProvider>(
builder: (context, provider, child) {
return StreamProvider<ItemsProvider>(
builder: (_) async* {
ItemsProvider _itemsProvider = Provider.of<ItemsProvider>(context);
await _itemsProvider.getItems();
yield _itemsProvider;
},
child: LeContent(),
updateShouldNotify: (prev, next) => true,
);
},
);
I expect the list to update whenever I receive an FCM notification

The builder parameter is called exactly once for the entire life of the StreamProvider.
The fact that you called Provider.of<ItemsProvider> doesn't change anything here – the method still won't be called again, even if ItemsProvider changes.
If you insist in using StreamProvider, you'll need to somehow transform the Provider.of into a stream instead.
You can use a StatefulWidget to do so. Here's an example:
class ProviderToStream<T> extends StatefulWidget {
const ProviderToStream({Key key, this.builder, this.child}) : super(key: key);
final ValueWidgetBuilder<Stream<T>> builder;
final Widget child;
#override
_ProviderToStreamState<T> createState() => _ProviderToStreamState<T>();
}
class _ProviderToStreamState<T> extends State<ProviderToStream> {
final StreamController<T> controller = StreamController<T>();
#override
void dispose() {
controller.close();
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
controller.add(Provider.of<T>(context));
}
#override
Widget build(BuildContext context) {
return widget.builder(context, controller.stream, widget.child);
}
}
You can then do:
ProviderToStream<Foo>(
builder: (_, stream, __) {
return StreamProvider(
builder: (_) async* {
await for (final value in stream) {
// TODO: yield something
}
}
);
}
)

I solved my own issue by having a StreamBuilder in a StatefulWidget.
I have my fetcher as a separate Future<T> method which will be called on initState and on didUpdateWidget().
Next, in my StatefulWidget class, I require something that will be used to validate on didUpdateWidget()'s oldWidget.something != widget.something.
In my case I used the length of the NoticeProvider.notices which will be incremented every time my FCM configuration triggers it.
in code:
#override
void didUpdateWidget(_Body oldWidget) {
if (oldWidget.alertLength != widget.alertLength) _fetchData();
super.didUpdateWidget(oldWidget);
}
_fetchData, will then call the api, transform the JSON, and add it to the stream using a StreamController which, in turn, update the UI.
I hope someone will be helped by this answer in the Future!

Related

Is there a way to trigger a BlocListener just after its initialization?

I'm working with Bloc and Hydrated Bloc and at some point in my app I want to store a boolean variable "firstTime" in a Hydrated Bloc to know if it's the first time my user is using the app. If it is the case, I redirect the user to a on-boarding page (called IntroPage), and if not, the login screen is displayed.
I use a BlocListener to listen to the changes of "firstTime", so once my user has finished navigating the on-boarding page, it redirects to the login screen.
#override
Widget build(BuildContext context) {
return MaterialApp(
...
builder: (context, child) {
return BlocListener<UserPreferencesBloc, UserPreferencesState>(
listener: (context, state) {
if (state.firstTime) {
_navigator.pushAndRemoveUntil<void>(
IntroPage.route(),
(route) => false,
);
}
},
child: child,
);
},
onGenerateRoute: (_) => SplashPage.route(),
);
}
The main problem is that if there's no change in the state of the Bloc, it does not fire the BlocListener part. The user never access the IntroPage.
Is there a way to make it so I can get into that listener just after its initialization, even without any change in the state of the Bloc ? Or is there another way to do that (that doesn't involve the use of Shared Preferences or other packages) ?
Edit : Here is the code for the Bloc :
class UserPreferencesBloc
extends HydratedBloc<UserPreferencesEvent, UserPreferencesState> {
UserPreferencesBloc() : super(const UserPreferencesState()) {
on<UserPreferencesFirstTimed>(_onFirstTime);
}
void _onFirstTime(
UserPreferencesFirstTimed event,
Emitter<UserPreferencesState> emit,
) async {
emit(state.copyWith(firstTime: event.firstTime));
}
#override
UserPreferencesState? fromJson(Map<String, dynamic> json) {
return UserPreferencesState(firstTime: json['firstTime'] as bool);
}
#override
Map<String, dynamic>? toJson(UserPreferencesState state) => {
'firstTime': state.firstTime,
};
}
And here is the state :
part of 'user_preferences_bloc.dart';
class UserPreferencesState extends Equatable {
const UserPreferencesState({
this.firstTime = true,
});
final bool firstTime;
UserPreferencesState copyWith({
bool? firstTime,
}) {
return UserPreferencesState(
firstTime: firstTime ?? this.firstTime,
);
}
#override
List<Object> get props => [firstTime];
}
And the Bloc is initialized in the app.dart file, at the start of the application :
class App extends StatelessWidget {
const App({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: ... //not shown in this piece of code
child: MultiBlocProvider(
providers: [
...
BlocProvider(create: (_) => UserPreferencesBloc())
],
child: AppView(),
),
);
}
}
It is by design so that BlocListener is only triggered once per state change.
But there are of course ways to do what you are after. If you'd show how you provide/create the bloc and also the definition of the state it could help...
But you could for instance let firstTime be nullable and use the cascade notion operator (..) when creating the bloc to immediately call a method in the bloc that sets the value of firstTime to true/false after initialization.
Edit:
Obviously hard from here to write all the changes you'd have to make, but here is the main idea:
Change: final bool firstTime; to bool? firstTime; and handle the null cases where applicable.
On creation, change:
BlocProvider(create: (_) => UserPreferencesBloc())
to:
BlocProvider(create: (_) => UserPreferencesBloc()..onFirstTime())
Write the method onFirstTime() something like this:
void onFirstTime() async {
emit(state.copyWith(firstTime: state.firstTime ?? true));
}
And remove the on<UserPreferencesFirstTimed>(_onFirstTime); part as well as this.firstTime = true,

How can I refresh the page when the user comes back to the app (from background)?

Right now the only way my app can refresh (it's a news app so it needs to constantly refresh) is two ways: 1) Refresh by scrolling up or 2) Restart the app after killing it from background.
I want to make it so that when a user just comes back to the app (say I'm using my app, then I go to WeChat to send a text, then I come back), the app is refreshed.
This is the refresh scroll code.
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Then it calls this function:
Future<void> _refresh() async {
print("Refreshed");
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(),
),
).then((value) => null);
}
What should I do to achieve what I need?
Is there a way to check if someone has "returned" to my app? Then I can just call the function.
You need to subscribe to the application life cycle. In Flutter it's not build in the same was it is natively. However, there is a good article I stumbled across a last year, trying to accomplish the same thing:
https://medium.com/pharos-production/flutter-app-lifecycle-4b0ab4a4211a
The gist of the implementation looks like this:
class MyHomePage extends StatefulWidget {
#override _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('state = $state');
}
#override
Widget build(BuildContext context) {
return YourWidget();
}
}
A better solution is to give the child widget of your FutureBuilder or StreamBulder a random unique key. This forces the underlying widget to redraw (and trigger the future or stream) when you come back from the background.
key: UniqueKey(),
Example:
return FutureBuilder<PriceData>(
future: DbService.getPriceData(),
builder: (context, priceData) {
if (priceData.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
return AnimatedAppear(
key: UniqueKey(), <---- here!

Flutter: How to fetch data from api only once while using FutureBuilder?

How can I fetch data only once while using FutureBuilder to show a loading indicator while fetching?
The problem is that every time the user opens the screen it will re-fetch the data even if I set the future in initState().
I want to fetch the data only the first time the user opens the screen then I will use the saved fetched data.
should I just use a stateful widget with a loading variable and set it in setState()?
I'm using Provider package
Future<void> fetchData() async {
try {
final response =
await http.get(url, headers: {'Authorization': 'Bearer $_token'});......
and my screen widget:
class _MyScreenState extends State<MyScreen> {
Future<void> fetchData;
#override
void initState() {
fetchData =
Provider.of<Data>(context, listen: false).fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchData,
builder: (ctx, snapshot) =>
snapshot.connectionState == ConnectionState.done
? Consumer<Data>(
builder: (context, data, child) => Text(data.fetchedData)): Center(
child: CircularProgressIndicator(),
),
);
}
}
If you want to fetch the data only once even if the widget rebuilds, you would have to make a model for that. Here is how you can make one:
class MyModel{
String value;
Future<String> fetchData() async {
if(value==null){
try {
final response =
await http.get(url, headers: {'Authorization': 'Bearer $_token'});......
value=(YourReturnedString)
}
}
return value;
}
}
Don't forget to place MyModel as a Provider. In your FutureBuilder:
#override
Widget build(context) {
final myModel=Provider.of<MyModel>(context)
return FutureBuilder<String>(
future: myModel.fetchData(),
builder: (context, snapshot) {
// ...
}
);
}
A simple approach is by introducing a StatefulWidget where we stash our Future in a variable. Now every rebuild will make reference to the same Future instance:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future<String> _future;
#override
void initState() {
_future = callAsyncFetch();
super.initState();
}
#override
Widget build(context) {
return FutureBuilder<String>(
future: _future,
builder: (context, snapshot) {
// ...
}
);
}
}
Or you can simply use a FutureProvider instead of the StatefulWidget above:
class MyWidget extends StatelessWidget {
// Future<String> callAsyncFetch() => Future.delayed(Duration(seconds: 2), () => "hi");
#override
Widget build(BuildContext context) {
// print('building widget');
return FutureProvider<String>(
create: (_) {
// print('calling future');
return callAsyncFetch();
},
child: Consumer<String>(
builder: (_, value, __) => Text(value ?? 'Loading...'),
),
);
}
}
You can implement provider and pass data among its child.
Refer this example for fetching the data once and using it throughout its child.
As Aashutosh Poudel suggested, you could use an external object to maintain your state,
FOR OTHERS COMING HERE!
To manage state for large applications, the stateful widgets management becomes a bit painful. Hence you have to use an external state object that is shall be your single source of truth.
State management in flutter is done by the following libraries | services:
i. Provider: Well, i have personally played with this a little bit, even did something with it. I could suggest this for beginners.
ii. GetX: That one library that can do everything, its a good one and is recommended for novice || noob.
iii. Redux: For anyone coming from the react and angular world to flutter, this is a very handy library. I personally love this library, plus when you give it additional plugins, you are just superman
iv. Bloc: Best for data that is in streams. in other words, best for reactive programming approach....
Anyways, that was a lot given your question. Hope i helped

How to reload the page whenever the page is on screen - flutter

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

Canonical way to use FutureBuilder with NetworkImage

I would like to be able to get to a network image within a single microtask if the image is already loaded. However, with the current API available in NetworkImage and FutureBuilder, this does not seem to be possible.
This is how we typically wire the two:
NetworkImage imageProvider = getSomeNetworkImage(id);
Completer<ui.Image> completer = Completer<ui.Image>();
imageProvider.resolve(ImageConfiguration()).addListener(
(ImageInfo info, _) => completer.complete(info.image));
return FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext futureBuilderContext, AsyncSnapshot<ui.Image> snapshot) {
if (!snapshot.hasData) {
return _buildPlaceholder();
} else {
return _buildActual(context, snapshot.data, imageProvider);
}
},
);
addListener() immediately calls completer.complete() if the image is already there. However, FutureBuilder is based off of completer.future which does not complete until the next microtask. So even when the image is available, placeholder is displayed momentarily.
What is the best way to avoid this? Perhaps, imageProvider should expose a Future that prevents us from piping this through a completer?
Instead of using a FutureBuilder, I would take advantage of the syncCall argument passed to the listener of the ImageStream. This will tell you if the image resolved immediately, meaning it is already cached. Otherwise you can call setState and trigger a rebuild when it does complete.
class Example extends StatefulWidget {
const Example({Key key, this.image, this.child}): super(key: key);
final ImageProvider image;
final Widget child;
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
bool _isImageLoaded = false;
#override
void initState() {
widget.image
.resolve(const ImageConfiguration)
.addListener(_handleResolve);
super.initState();
}
#override
Widget build(BuildContext context) {
// if syncCall = true, then _handleResolve will have already been called.
if (_isImageLoaded)
return new Image(widget.image);
return widget.child;
}
void _handleResolve(ImageInfo info, bool syncCall) {
_isImageLoaded = true;
if (!syncCall) {
// we didn't finished loading immediately, call setState to trigger frame
setState(() { });
}
}
}