How can I recall a futureprovider (riverpod) on build - flutter

I have a simple page, which shows a product, and i am trying to use riverpod, and futureprovider, but the future is only called the first time i go to the page?
final productInfo = FutureProvider<Map<String, dynamic>>((ref) async {
final response =
await http.get('https://www.api.com/$clickedID');
final content = json.decode(response.body) as Map<String, dynamic>;
return content;
});

With the recent Riverpod, you can do ref.refresh(userProvider) to refresh the provider.

Please use
final userProvider = StreamProvider<User>(
final response =
await http.get('https://www.api.com/$clickedID');
final content = json.decode(response.body) as Map<String, dynamic>;
return content;
);
now you can listen to the provider using watch,
AsyncValue<User> user = watch(userProvider);

No inbuilt functionality, but you can override the value with the same future at a later time.
recallProductInfoFutureProvider() {
final response = http.get('https://www.api.com/$clickedID');
final content = json.decode(response.body) as Map<String, dynamic>;
productInfo.overrideWithValue(content)
}
Consider using with StreamProvider and watching inside Consumer to update UI on overrideWithValue.

There are a two options.
ref.refresh(provider)
ref.invalidate(provider)
Note that if you are using a AsyncValue, the old result will be returned when you refresh the FutureProvider and AsyncValue.isRefreshing will be set to true:
final notificationRepositoryProvider = FutureProvider<bool?>((ref) async {
Future<bool> approveDocument() => Future.delayed(Duration(seconds: 1), () => Future.value(Random().nextBool()));
return approveDocument();
});
class HomeView extends ConsumerStatefulWidget {
const HomeView({Key? key}) : super(key: key);
#override
HomeViewState createState() => HomeViewState();
}
class HomeViewState extends ConsumerState<HomeView> {
#override
Widget build(BuildContext context) {
AsyncValue<bool?> rejectResponse = ref.watch(notificationRepositoryProvider);
return ElevatedButton(
onPressed: () {
// ref.refresh(notificationRepositoryProvider);
ref.watch(notificationRepositoryProvider).;
},
child: rejectResponse.when(
loading: () => const CircularProgressIndicator(
color: Colors.white,
),
skipLoadingOnRefresh: false,
error: (err, stack) => Text('Error'),
data: (data) => Text('Yes: $data'),
));
}
}

Related

How can I use streamProvider with StateNotifier together?

I have WebSocket service which I want to use with StreamProvider. When I used it with simple StreamProvider and tried to get data it worked well as it was described in docs, but my issue is a little bit complicated: I want to make it reactive and change seconds (I get DateTime from WebSocket). So I found out that in riverpod we can use state with StateNotifier and using it I can change state. So, I decided to combine two kinds of providers (in docs it says I can easily do it), but when I placed StreamProvider inside StateNotifier, it stopped getting requests to website and retrieve data. How can I solve this issue?
My full code looks like this:
final todosProvider =
StateNotifierProvider<TickingTime, String?>((ref) => TickingTime());
class TickingTime extends StateNotifier<String?> {
static String? time;
static var websockets;
TickingTime() : super(time) {
Timer.periodic(const Duration(seconds: 1), (timer) {
state = time;
});
print('NOT HEHE1');
websockets = StreamProvider((ref) async* {
print('NOT HEHE');
final httpConnectionOptions = HttpConnectionOptions(
accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
skipNegotiation: true,
transport: HttpTransportType.WebSockets);
final connectionValue = HubConnectionBuilder()
.withUrl(
'http://securelink/link',
options: httpConnectionOptions,
)
.build();
print('NOT HEHE2');
await connectionValue.start();
if (connectionValue.state == HubConnectionState.Connected) {
await connectionValue.invoke('GetCurrentDateTime').then((value) {
time = value as String;
DateTime dateTime = DateTime.parse(time ?? 'no time');
time = DateFormat('HH:mm:ss').format(dateTime);
});
yield time;
}
;
ref.onDispose(() {
connectionValue.onclose(({error}) {
throw Exception('NOT HEHE, YOU KNOW');
});
});
});
}
}
UI:
class MyApp extends HookConsumerWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
var time = ref.watch(todosProvider);
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(time ?? 'NOTHING TO SHOW'),
),
),
);
data I get from WebSocket:
14:25:34

How to re-render a Widget based on another widget using riverpod in flutter?

I want to know how can I refresh a table data (which is fetched from an API using a future provider) and re-render the table widget based on dropdown value change.
Following is the Repo file with providers:
import 'package:ct_analyst_app/src/features/dashboard/domain/dashboard_data.dart';
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../authentication/application/auth_local_service.dart';
abstract class IDashboardRepository {
Future<void> fetchDashboard(String name);
Future<void> fetchNames();
}
final clientProvider = Provider.family((ref, token) => Dio(BaseOptions(
baseUrl: "http://${dotenv.env['IP']}/excel/",
headers: {"authorization": token})));
class DashboardRepository implements IDashboardRepository {
DashboardRepository(this.read);
final Reader read;
DashboardData? _data;
DashboardData? get dashboardData => _data;
List<dynamic>? _names;
List<dynamic>? get names => _names;
#override
Future<DashboardData?> fetchDashboard(String name) async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token))
.get('/getData', queryParameters: {"name": name});
_data = DashboardData.fromJson(response.data);
print(name);
return _data;
}
#override
Future<void> fetchNames() async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token)).get('/analystNames');
_names = response.data["names"];
}
}
final dashboardRepositoryProvider =
Provider((ref) => DashboardRepository(ref.read));
final fetchDashboardData = FutureProvider.family<void, String>((ref, name) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchDashboard(name);
});
final fetchAnalystNames = FutureProvider((ref) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchNames();
});
I have tried to refresh the future provider in the dropdown onChange and it does fetch the new table data from the API. However, the widget which renders the data in the table is not getting re-rendered when the refresh is called.
Done as following:
onChanged: (String? newValue) {
ref.read(dropItemProvider.notifier).state = newValue as String;
ref.refresh(fetchDashboardData(newValue));
setState(() {
widget.value = newValue;
});
},
I am using ref.watch on the data, still it does not re-render the widget if the data is changed.
class TableGenerator extends ConsumerWidget {
const TableGenerator({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(dashboardRepositoryProvider);
return data.dashboardData != null
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
const FixedColumnWidget(data: [
"one",
"two",
"three",
"four",
"fifth",
]),
ScrollableColumnWidget(
data: data.dashboardData as DashboardData)
],
))
: const CircularProgressIndicator();
}
}
Am I missing something or how should I approach this problem? like different providers or something else?
Thanks!
Your Widget is watching dashboardRepositoryProvider, which doesn't update after the ref.refresh call.
There's two things to consider:
dashboardRepository should just expose your repo / services, and instead it is used to observe actual data. This is not affecting your app directly, but it is part of the problem imho. I'd expect your Widget to observe a FutureProvider that exposes (and caches, etc.) the data by calling the methods inside your repository;
Then, let's analyze why your Widget isn't updating: dashboardRepository isn't depending, i.e. performing a watch, on the Provider you're refreshing, which is fetchDashboardData, nor it is depending on dropItemProvider (I am specifying this since your onChanged callback updates / refreshes two different Providers).
I think your should refactor your code so that it will expose actual data from a FutureProvider which exploits your repositories and can be simply refreshed similarly as what you already are doing.
Quick FutureProvider example:
// WARNING: PSEUDOCODE
final myDataProvider = FutureProvider<MyClass>((ref) {
final repo = ref.watch(myRepo);
final response = repo.getSomeData(...);
// TODO: add error handling, debouncing, cancel tokens, etc.
return MyClass.fromJson(response.data); // e.g.
});
Quick usage:
// WARNING: PSEUDOCODE
#override
Widget build(BuildContext context, WidgetRef ref) {
final myData = ref.watch(myDataProvider);
return ElevatedButton(
onTap: () {
ref.refresh(myDataProvider);
},
child: Text("Click me to refresh me (data: $myData)"),
);
}

Future Provider Stuck In loading state

I am using a future provider to display a login page on load and then a loading indicator on loading. Here is my future provider
final loginProvider = FutureProvider.family((ref, UserInput input) =>
ref.read(authRepositoryProvider).doLogin(input.email, input.password));
In my UI I have this....
class LoginScreen extends HookWidget {
final TextEditingController emailEditingController = TextEditingController();
final TextEditingController passwordEditingController =
TextEditingController();
#override
Widget build(BuildContext context) {
var userInput =
UserInput(emailEditingController.text, passwordEditingController.text);
final login = useProvider(loginProvider(userInput));
return login.when(
data: (user) => Login(emailEditingController, passwordEditingController),
loading: () => const ProgressIndication(),
error: (error, stack) {
if (error is DioError) {
return Login(emailEditingController, passwordEditingController);
} else {
return Login(emailEditingController, passwordEditingController);
}
},
);
}
}
here is my doLogin function.
#override
Future<dynamic> doLogin(String email, String password) async {
try {
final response = await _read(dioProvider)
.post('$baseUrl/login', data: {'email': email, 'password': password});
final data = Map<String, dynamic>.from(response.data);
return data;
} on DioError catch (e) {
return BadRequestException(e.error);
} on SocketException {
return 'No Internet Connection';
}
}
I would like to know why it's stuck in the loading state. Any help will be appreciated.
First off, family creates a new instance of the provider when given input. So in your implementation, any time your text fields change, you're generating a new provider and watching that new provider. This is bad.
In your case, keeping the UserInput around for the sake of accessing the login state doesn't make a lot of sense. That is to say, in this instance, a FamilyProvider isn't ideal.
The following is an example of how you could choose to write it. This is not the only way you could write it. It is probably easier to grasp than streaming without an API like Firebase that handles most of that for you.
First, a StateNotifierProvider:
enum LoginState { loggedOut, loading, loggedIn, error }
class LoginStateNotifier extends StateNotifier<LoginState> {
LoginStateNotifier(this._read) : super(LoginState.loggedOut);
final Reader _read;
late final Map<String, dynamic> _user;
static final provider =
StateNotifierProvider<LoginStateNotifier, LoginState>((ref) => LoginStateNotifier(ref.read));
Future<void> login(String email, String password) async {
state = LoginState.loading;
try {
_user = await _read(authRepositoryProvider).doLogin(email, password);
state = LoginState.loggedIn;
} catch (e) {
state = LoginState.error;
}
}
Map<String, dynamic> get user => _user;
}
This allows us to have manual control over the state of the login process. It's not the most elegant, but practically, it works.
Next, a login screen. This is as barebones as they get. Ignore the error parameter for now - it will be cleared up in a moment.
class LoginScreen extends HookWidget {
const LoginScreen({Key? key, this.error = false}) : super(key: key);
final bool error;
#override
Widget build(BuildContext context) {
final emailController = useTextEditingController();
final passwordController = useTextEditingController();
return Column(
children: [
TextField(
controller: emailController,
),
TextField(
controller: passwordController,
),
ElevatedButton(
onPressed: () async {
await context.read(LoginStateNotifier.provider.notifier).login(
emailController.text,
passwordController.text,
);
},
child: Text('Login'),
),
if (error) Text('Error signing in'),
],
);
}
}
You'll notice we can use the useTextEditingController hook which will handle disposing of those, as well. You can also see the call to login through the StateNotifier.
Last but not least, we need to do something with our fancy new state.
class AuthPage extends HookWidget {
const AuthPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final loginState = useProvider(LoginStateNotifier.provider);
switch (loginState) {
case LoginState.loggedOut:
return LoginScreen();
case LoginState.loading:
return LoadingPage();
case LoginState.loggedIn:
return HomePage();
case LoginState.error:
return LoginScreen(error: true);
}
}
}
In practice, you're going to want to wrap this in another widget with a Scaffold.
I know this isn't exactly what you asked, but thought it might be helpful to see another approach to the problem.

How to access data in Bloc's state from another bloc

I am developing a Flutter application using Bloc pattern. After success authentication, UserSate has User object. In all other Blocs, I need to access User object in UserState. I tried with getting UserBloc on other Bloc's constructor parameters and accessing User object. But it shows that User object is null. Anyone have a better solution?
class SectorHomeBloc extends Bloc<SectorHomeEvent, SectorHomeState> {
final OutletRepository outletRepository;
UserBloc userBloc;
final ProductRepository productRepository;
final ProductSubCategoryRepository productSubCategoryRepository;
final PromotionRepository promotionRepository;
final ProductMainCategoryRepository mainCategoryRepository;
SectorHomeBloc({
#required this.outletRepository,
#required this.userBloc,
#required this.productSubCategoryRepository,
#required this.productRepository,
#required this.promotionRepository,
#required this.mainCategoryRepository,
});
#override
SectorHomeState get initialState => SectorHomeLoadingState();
#override
Stream<SectorHomeState> mapEventToState(SectorHomeEvent event) async* {
try {
print(userBloc.state.toString());
LatLng _location = LatLng(
userBloc.state.user.defaultLocation.coordinate.latitude,
userBloc.state.user.defaultLocation.coordinate.longitude);
String _token = userBloc.state.user.token;
if (event is GetAllDataEvent) {
yield SectorHomeLoadingState();
List<Outlet> _previousOrderedOutlets =
await outletRepository.getPreviousOrderedOutlets(
_token, _location, event.orderType, event.sectorId);
List<Outlet> _featuredOutlets =
await outletRepository.getFeaturedOutlets(
_token, _location, event.orderType, event.sectorId);
List<Outlet> _nearestOutlets = await outletRepository.getOutletsNearYou(
_token, _location, event.orderType, event.sectorId);
List<Product> _newProducts = await productRepository.getNewItems(
_token, _location, event.orderType, event.sectorId);
List<Product> _trendingProducts =
await productRepository.getTrendingItems(
_token, _location, event.orderType, event.sectorId);
List<Promotion> _promotions = await promotionRepository
.getVendorPromotions(_token, event.sectorId);
yield SectorHomeState(
previousOrderedOutlets: _previousOrderedOutlets,
featuredOutlets: _featuredOutlets,
nearByOutlets: _nearestOutlets,
newItems: _newProducts,
trendingItems: _trendingProducts,
promotions: _promotions,
);
}
} on SocketException {
yield SectorHomeLoadingErrorState('could not connect to server');
} catch (e) {
print(e);
yield SectorHomeLoadingErrorState('Error');
}
}
}
The print statement [print(userBloc.state.toString());] in mapEventToState method shows the initial state of UserSate.
But, at the time of this code executing UserState is in UserLoggedInState.
UPDATE (Best Practice):
please refer to the answer here enter link description here
so the best way for that is to hear the changes of another bloc inside the widget you are in, and fire the event based on that.
so what you will do is wrap your widget in a bloc listener and listen to the bloc you want.
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<FirstBloc, FirstBlocState>(
listener: (context, state) {
if(state is StateFromFirstBloc){
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());}//or whatever you want
},
child: ElevatedButton(
child: Text('THIS IS NEW SCREEN'),
onPressed: () {
BlocProvider.of<SecondBloC>(context).add(SecondBloCEvent());
},
),
);
}
}
the lovely thing about listener is that you can listen anywhere to any bloc and do whatever you want
here is the official documentation for it
OLD WAY (NOT Recommended)
there is an official way to do this as in the documentation, called Bloc-to-Bloc Communication
and here is the example for this as in the documentation
class MyBloc extends Bloc {
final OtherBloc otherBloc;
StreamSubscription otherBlocSubscription;
MyBloc(this.otherBloc) {
otherBlocSubscription = otherBloc.listen((state) {
// React to state changes here.
// Add events here to trigger changes in MyBloc.
});
}
#override
Future<void> close() {
otherBlocSubscription.cancel();
return super.close();
}
}
sorry for the late update for this answer and thanks to #MJ studio
The accepted answer actually has a comment in the above example in the official docs saying "No matter how much you are tempted to do this, you should not do this! Keep reading for better alternatives!"!!!
Here's the official doc link, ultimately one bloc should not know about any other blocs, add methods to update your bloc and these can be triggered from blocListeners which listen to changes in your other blocs: https://bloclibrary.dev/#/architecture?id=connecting-blocs-through-domain
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<WeatherCubit, WeatherState>(
listener: (context, state) {
// When the first bloc's state changes, this will be called.
//
// Now we can add an event to the second bloc without it having
// to know about the first bloc.
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
},
child: TextButton(
child: const Text('Hello'),
onPressed: () {
BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
},
),
);
}
}

Flutter send variable to the other class

I have the below class like.dart that need to send some data to home.dart, the start page is the home dart and after click on button on post navigate to other page like.dart with parameters, after user click on like I want to update the view on home.dart to show that user liked this post
class imageModal extends StatefulWidget {
//imageModal({Key key}) : super(key: key);
final title;
final comments;
final links;
final urlImages;
final userid;
final postid;
final userlikes;
int totallikes;
final like;
imageModal(this.title,this.comments,this.links,
this.urlImages,this.postid,this.userid,this.userlikes,this.totallikes,this.like);
#override
imageModalState createState() => imageModalState();
}
...
new IconButton(
icon: new Icon(Icons.favorite_border, color: Colors.black) ,
onPressed: (){
_like(widget.postid,state);
}
),
...
_like(postid,StateSetter updateState) async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
var userid = jsonDecode(localStorage.getString('userid'));
var user = jsonDecode(localStorage.getString('user'));
var data = {
'userid' : userid,
'postid': postid,
};
var res = await Network().like(data, 'post/like/');
var body = json.decode(res.body);
print(body['data']['users']);
updateState(() {
widget.like.add(body['data']);
//print( posts[i]['like']);
widget.userlikes.add(body['data']);
var totallikes = widget.totallikes;
var finallikes = totallikes+1;
widget.totallikes = finallikes;
});
}
I want to send to the home.dart the below values new values
widget.like
widget.userlikes
widget.totallikes
and when I send them to setstate them so can update the view
If you want to return from the other screen to home with the updated values, you can use Navigator.pop(context, result);
In FirstScreen
_navigateToAnotherScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
// print(result);
}
In SecondScreen
Navigator.pop(context, [value1, value2, value3]);
Use state management technique like Provider
https://pub.dev/packages/provider