How to call a riverpod provider outside of widget tree/widget class? - flutter

I am new to using RiverPod. previously I used Provider for state management.
in case of provider I could use a provider outside widget tree to get value using syntax
Provider.of<MyModel>(context,listen:true).someFunction();
how do I do the same in RiverPod? for now I am using Consumer Builder and Consumer Widget. I was wondering if there's a way to call a riverpod provider without using Consumer.

You can easily call riverpord provider without using Consumer. Please check out my below extension which is very helpful to call the provider based on context where it will work for the read function without any consumer or consumer widget.
extension Context on BuildContext {
// Custom call a provider for reading method only
// It will be helpful for us for calling the read function
// without Consumer,ConsumerWidget or ConsumerStatefulWidget
// Incase if you face any issue using this then please wrap your widget
// with consumer and then call your provider
T read<T>(ProviderBase<T> provider) {
return ProviderScope.containerOf(this, listen: false).read(provider);
}
}
After then under the build method, you must call context.read(yourProviderName)

let's say you have a separate class that is responsible for ui logic:
class UiController {
UiController(this._ref);
final Ref _ref;
Reader get _reader => _ref.read;
static final pr = Provider<UiController>((ref) => UiController(ref));
void someFunction() {
_reader(otherProvider).getValue();
}
}
from a widget or other functions where ref is accessed, you can call:
ref.read(UiController.pr).someFunction();

Related

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

Flutter: Invoking Provider method in another class outside of the widget tree

I am currently using Provider in my Flutter project to manage the authentication state of my mobile app.
The model for AuthProvider is as follow:
lib/models/auth_provider.dart
class AuthProvider with ChangeNotifier {
// Some methods and properties are removed for simplicity.
// ...
bool loggedIn;
void allowAccess() {
loggedIn = true;
notifyListeners();
}
void revokeAccess() {
loggedIn = false;
notifyListeners();
}
}
The application uses some services from another class to check the validity of the authentication token.
If the the token is not valid anymore, the method in the service in another class will need to revoke the access:
lib/services/auth_services.dart
import 'package:exampleapp/shared/global_context.dart' as global_context;
class AuthService {
// Some methods and properties are removed for simplicity.
// ...
void checkValidity() {
// ...
if(notValid) {
// Use provider to revoke access
Provider.of<AuthProvider>(
global_context.GlobalContext.globalContext!, listen: false)
.revokeAccess();
}
}
}
To achieve this (since there is no context outside of the widget tree), the app uses a global context to allow the services file to invoke the Provider method:
lib/shared/global_context.dart
import 'package:flutter/material.dart';
class GlobalContext {
static BuildContext? globalContext;
}
And add the following line in the build method of the widget that might involves auth state change:
global_context.GlobalContext.globalContext = context;
I read that we're not advised to access Provider outside the widget tree and I don't think using a GlobalContext is the best practice. Is there any other way that I could do this by using Provider?
P/S: I'm still learning Flutter, please comment below if any clarification is needed.
I usually use the get_it package, It gives the ability to call Provider without needing to specifiy a particular context.
https://pub.dev/packages/get_it
First I would call setupLocator()
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator
.registerLazySingleton(() => AuthProvider());
}
Then use it like this..
class SomeClass {
final _provider = locator<AuthProvider>();
void someMethod(){
_provider.revokeAccess();
}
}
This is a very good and important question obviously there are tons of approaches that we can follow here, you can use a global context but you have to make sure it's always the correct context so whenever you push/pop different routes you have to also sync the context
One easy approach i think would be to add the context to checkValidity() function as a parameter
Also i would recommend checking out stacked state management solution https://pub.dev/packages/stacked its perfect for situations like this.

Riverpod - ref.onDispose called when object is created, and not when disposed

I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.

using didUpdateWidget to replace a stream causes "bad state: Stream has already been listened to"

I have a StreamBuilder that I want to be able to pause and unpause and, since StreamBuilder doesn't expose it's underlying StreamSubscription, I have to create a proxy stream to forward the source stream's elements while exposing a StreamSubscription that I can pause and unpause. This forces me to convert my stateless widget into a stateful widget so I have access to dispose and can close it's StreamController.
I'm following the didUpdateWidget documentation's instructions:
In initState, subscribe to the object.
In didUpdateWidget unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object.
In dispose, unsubscribe from the object.
I need to replace the Stream whenever the user types in a new search query. My stateful widget is re-constructed within a ChangeNotifier whenever a new stream is received.
Here's the relevant part of my stateful widget:
final Stream<List<Entry>> _entryStream;
StreamController<List<Entry>> _entryStreamController;
StreamSubscription<List<Entry>> _entryStreamSubscription;
_EntryListState(this._entryStream);
void initStream() {
_entryStreamController?.close();
_entryStreamController = StreamController();
_entryStreamSubscription = _entryStream.listen((event) {
_entryStreamController.add(event);
}
)..onDone(() => _entryStreamController.close());
}
#override
void initState() {
initStream();
super.initState();
}
#override
void dispose() {
_entryStreamController?.close();
super.dispose();
}
#override
void didUpdateWidget(EntryList oldEntryList) {
// Initialize the stream only if it has changed
if (oldEntryList._entryStream != _entryStream) initStream();
super.didUpdateWidget(oldEntryList);
}
This is the line throwing the BadState error:
_entryStreamSubscription = _entryStream.listen((event) {
_entryStreamController.add(event);
}
And here is where my widget is being constructed:
child: Consumer<SearchStringModel>(
builder: (context, searchStringModel, child) {
print('rebuilding with: ${searchStringModel.searchString}');
var entryStream = _getEntries(searchStringModel.searchString);
return EntryList(entryStream);
}
),
I don't think I understand didUpdateWidget in particular and suspect that's where the two issues (bad state, and not updating) are coming from? I'm also re-constructed the stateful widget instead of just modifying its state which is a little confusing, but the widget is intended to act as a stateless widget for all intents and purposes so it'd be a pain to update it to update state instead of reconstructing. Any advice?
I had not read the "didUpdateWidget" documentation closely enough. This is the key paragraph:
/// 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.
In my consumer, I'm rebuilding with a new EntryList. As the above paragraph describes, the framework will then call didUpdateWidget on the same state object (as opposed to a new one). In order to check whether or not the configuration has changed and, if it has, to access the new configuration, I need to access the widget property of my state object. With this in mind, here is my new implementation of didUpdateWidget:
#override
void didUpdateWidget(EntryList oldEntryList) {
super.didUpdateWidget(oldEntryList);
// Initialize the stream only if it has changed
if (widget._entryStream != _entryStream) {
_entryStream = widget._entryStream;
initStream();
}
}
Note that I am now checking widet._entryStream and then setting the state's _entryStream to it if it's changed.
Hopefully someone else running into a similar problem will be helped by this!

Handling api response with bloc pattern with rxdart

Im a bit confused on how rxdart works with bloc pattern. This is a code i copied from a youtube channel. Its a bloc that has a method which returns an API response. There's usually a mapEventToState method somewhere in the bloc but this one doesn't. I've added some comments to show what i understand and hope you guys can correct me. Thanks.
Source code: https://github.com/bilguunint/igdb/blob/master/lib/bloc/get_games_bloc.dart
class GetGamesBloc {
final GameRepository _repository = GameRepository(); // defining the api repository
final BehaviorSubject<GameResponse> _subject = BehaviorSubject<GameResponse>();
// defining a behaviour stream which will give only the latest item/data
getGames(int platformId) async {
GameResponse response = await _repository.getGames2(platformId);
_subject.sink.add(response);
}
// this method fetches the api data but not sure why add response to the sink. Isnt sink suppose to be an event? The response is an api json data so it's a stream right ?
dispose() {
_subject.close();
}
//closing the stream when not in use to prevent memory loss
BehaviorSubject<GameResponse> get subject => _subject;
// defining a getter to be used outside the class
}
final getGamesBloc = GetGamesBloc();
// I think this enables us to use the bloc as getGamesBloc ?
The point of BLoC pattern is to separate business logic from views to keep the code clean, readable and testable. The mapEventToState has the responsibility of converting events to state and you can use any other alternatives to do that. In Cubit from bloc package we're defining functions to emit and change state.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
In the example you provided, It's defining a function which will change the state. So, as far as I know that's correct and it counts as a business logic component.