Flutter RiverPod: Is it ok to return another provider from build method of Notifier? - flutter

I want to keep my return value as AsyncValue rather than Stream so I am returning StreamProvider from build method of Notifier. After reading the codebase of riverpod I can't see any drawback of this, but I have never come across any project doing something like this. Is this fine, or is there any straight forward way to convert Stream to AsyncValue.
final _userProvider = StreamProvider.autoDispose<User?>((ref) {
final repository = ref.watch(repositoryProvider);
return repository.getUser(); //returns Stream<User?>
});
class AuthNotifier extends AutoDisposeNotifier<AsyncValue<User?>> {
#override
AsyncValue<User?> build() {
return ref.watch(_userProvider);
}
Future<void> singOut() {
return ref.read(repositoryProvider).signOut();
}
}
final authProvider =
AutoDisposeNotifierProvider<AuthNotifier, AsyncValue<User?>>(
AuthNotifier.new);

This is fine, yes.
Being able to do such a thing is the goal of the build method & ref.watch
As long as you don't return the provider itself but the value exposed by the provider, there is no problem:
build() {
return ref.watch(provider); // OK
}
build() {
return provider // KO
}

Related

how can I get the other controller's variable inside one controller in flutter using getx

This is an issue related to the getx in flutter.
I have 2 controllers. ContractsController and NotificationController.
In ContractsController I have put the value into observer variable by calling the Api request.
What I want now is to get that variable's data in another controller - NotificationController.
How to get that value using getx functions?
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the api
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
The expiringContracts is updated successfully with data after the api request.
Now, I want to get that value in NotificationController
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
contractsController = Get.find<ContractsController>();
print(contractsController.expiringContracts); // This shows an empty list ?????
super.onInit();
}
}
Overview
A couple solutions come to mind:
pass the expiringContracts list as a constructor argument to NotificationsController if you only need this done once at instantiation, or
use a GetX worker to update NotificationsController every time expiringContracts is updated
The first solution isn't related to GetX, rather it's just async coordination between ContractsController and NotificationsController, so lets focus on the 2nd solution: GetX Workers.
Details
In NotificationsController, create a method that will receive expiringContracts.
Something like:
class NotificationsController extends GetxController {
void refreshContracts(List<ExpiringContract> contracts) {
// do something
}
}
Please note: none of this code is tested. I'm writing this purely in StackOverflow, so consider this pseudo-code.
In ContractsController we'll supply the above callback method as a constructor arg:
In ContractsController, something like:
class ContractsController {
final expiringContracts = <ExpiringContract>[].obs
final Function(List<ExpiringContract>) refreshContractsCallback;
ContractsController(this.refreshContractsCallback);
#override
void onInit() {
super.onInit();
refreshContracts(); // do your stuff after super.onInit
ever(expiringContracts, refreshContractsCallback);
// ↑ contracts → refreshContractsCallback(contracts)
// when expiringContracts updates, run callback with them
}
}
Here the GetX ever worker takes the observable as first argument, and a function as 2nd argument. That function must take an argument of type that matches the observed variable, i.e. List<ExpiringContract>, hence the Type of refreshContractsCallback was defined as Function(List<ExpiringContract>).
Now whenever the observable expiringContracts is updated in ContractsController, refreshContractsCallback(contracts) will be called, which supplies the list of expiring contracts to NotificationsController via refreshContracts.
Finally, when instantiating the two controllers inside the build() method of your route/page:
NotificationsController nx = Get.put(NotificationsController());
ContractsController cx = Get.put(ContractsController(nx.refreshContracts));
Timeline of Events
NotificationsController gets created as nx.
nx.onInit() runs, slow call of refreshContracts() starts
ContractsController gets created, with nx.refreshContracts callback
your page paints
nx has no contracts data at this point, so you'll prob. need a FutureBuilder or an Obx/ GetX + StatelessWidget that'll rebuild when data eventually arrives
when refreshContracts() finishes, ever worker runs, sending contracts to nx
nx.refreshContracts(contracts) is run, doing something with contracts
Notes
async/await was removed from nx.onInit
ever worker will run when refreshContract finishes
There were some powerful approaches in GetX. I solved this issue with Get.put and Get.find
Here is the code that I added.
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the API
// ******************************** //
Get.put(ContractsController()); // Added here
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
// ******************************** //
contractsController = Get.find<ContractsController>(); // Added here.
print(contractsController.expiringContracts); // This shows the updated value
super.onInit();
}
}
Finally, I have found that GetX is simple but powerful for state management in flutter.
Thanks.

How to get the 'bool' value from a Future<bool> into a field variable, for later use

I am using flutter_blue package for using the Bluetooth service. I want to check whether the device has Bluetooth capabilities. The method isAvailable seems to do it. However, it returns a Future<bool>, which I am tryting to get into a variable as follows:
import 'package:flutter_blue/flutter_blue.dart';
class BT_Base {
final FlutterBlue _fb = FlutterBlue.instance;
bool BTAvailable = true; // as a default placeholder
BT_Base () {
BTAvailable = _fixAvail();
}
_fixAvail () async {
return await _fb.isAvailable;
}
...
I try to get the future value from it and store into BTAvailable. Later on, I use the fixed BTAvailable field to get the appropriate Widget to be passed onto as follows:
class BTDevicePrompt extends StatelessWidget {
#override
Widget build(BuildContext context) {
BT_Base bt = BT_Base();
var btDeviceRes = bt.scan();
if(!bt.BTAvailable) return Text('Bluetooth unavailable on device...');
else if (btDeviceRes.isEmpty) return Text('No Bluetooth devices in range...');
else {
return CupertinoActionSheet(
actions: [
...
],
)
}
}
}
But I keep getting the error type 'Future<dynamic>' is not a subtype of type 'bool' at runtime. How can I use the Future properly in this situation? It is alright if the whole process just halts and waits for this part as well.
I have gone through a lot of solutions but I am not able to piece it together.
Any method marked async always returns a Future of some kind. You can give it an explicit return type like Future<bool> function() async { ... }, or if you leave it out it will infer Future<dynamic>.
In short, you can't get a bool from a Future<bool> outside of an async function (there are technically ways but almost certainly not what you want in Flutter).
This makes sense, since the whole point of a Future<bool> is that it's going to be a bool in the future. If there was some process to convert from a Future<bool> to a bool, what should it do if the future has not yet completed? Perhaps it should wait until it has completed. In that case, you're just describing the await keyword.
If, however, you want to use a Future in your UI in a Flutter application, you have a few options.
The simplest for your case will be to move it into initState():
class BTDevicePrompt extends StatefulWidget {
// stateful widget boilerplate
}
class BTDevicePromptState extends State<BTDevicePrompt> {
bool isAvailable = false;
#override
void initState() {
super.initState();
checkAvailable(); // use a helper method because initState() cannot be async
}
Future<void> checkAvailable() async {
// call an async function and wait for it to complete
bool result = await checkIfBluetoothAvailable();
setState(() => bluetoothAvailable = result); // set the local variable
}
#override
Widget build(BuildContext context) {
if (bluetoothAvailable) return Text('bluetooth available');
else return Text('bluetooth not available');
}
}

Riverpod provider is always null

I am using riverpod for my state manegement in my flutter app.
Riverpod offers a feature for combined providers, but my dependent provider does not update and always returns null.
By clicking one of the pins (secrets) on the map, my "selectedSecretProvider" is updated (default is null). This should trigger the initialization of my audio player. And by clicking play, the sound of the current _selectedSecret should play. So my "selectedTrackProvder" is dependent on my "selectedSecretProvider":
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
Secret? selectedSecret = ref.watch(selectedSecretProvider);
return SelectedTrack(selectedSecret);
});
Here is my selectedTrack class:
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.selectedSecret) : super(Track.initial());
Secret? selectedSecret;
#override
void dispose() {
...
}
void initAudioPlayer() {
...
}
Future<int> play() async {
print(selectedSecret);
return ...
}
}
So why does it always print null, when clicking play?
(Btw. my bottom_panel_sheet is showing the correct data and also consumes the "selectedSecretProvider".)
I wouldn't say the way you're creating your StateNotifierProvider is wrong, but I think the following is a better approach that should solve your problem.
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
return SelectedTrack(ref);
});
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.ref) : super(Track.initial());
final ProviderReference ref;
Future<int> play() async {
final selectedSecret = ref.read(selectedSecretProvider);
print(selectedSecret);
return ...
}
}
This way you don't create a new StateNotifier every time the selectedSecretProvider updates, instead opting to read the current value of the selectedSecretProvider when attempting to call play.

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

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
}
}

Complete scoped model async call in initstate before widget builds

I have a requirement which is to make an API call when the page is opened and use the result of the API call to populate a widget. I have to cater for cases like when the call is being made, show a loader, if an error occurs, show a widget that allows the user to reload the page which calls that method again if the call is successful use the result to populate the widget.
This looks like a perfect use case for a FutureBuilder, however, should the call fail, there won't be a way to reload as FutureBuilder is executed once.
An alternative is to use a StreamBuilder where I just add to the stream the error or response, this allows me to reload the page if the call fails. The streambuidler approach works fine.
Initially, I've tried to use a ScopedModel approach by making the async API call in the initstate method, however, as expected the api call takes some time and the widget Build method is called so the page is built before the api call completes and when it's done, even if I call notifylisteners() the widget tree doesn't rebuild with the data from the API call. Is there a way to make this work using a ScopedModel approach?
DashboardService.dart
Future<string> getDashboardState() async{
try{
var response = await client.get('/getdashboardstate');
return response;
}catch(error){
return error;
}
}
DashboardModel.dart
Future<BaseDataResponse<MambuLoan>> getDashboardState() async {
try {
setState(ViewState.Busy);
var info = await _dashboardService.getDashboardState();
if (info.isSuccessful) {
return info;
} else {
setState(ViewState.Error);
return info;
}
} catch (error) {
setState(ViewState.Error);
return error;
}
}
DashboardView.dart
#override
void initState() {
// TODO: implement initState
super.initState();
getDashboard() // result of this set some data in the scopedmodel
}
Future<String> getDashboard() async{
var response = await dashboardModel.getDashboardState();
return response;
}
Widget Build(BuildContext context){
//gets data in scoped model to build widget
return Container() //actual widget here
}
UPDATE
I Was approaching this the wrong way. I ended up not needing a stateful widget but a stateless one in which I called my Future in the build method before returning a widget.