How to read the data from AsyncData<void> in Riverpod - flutter

I am trying to implement simple authentication using Riverpod package in flutter. State of my controller after calling the sign in function prints this: "AsyncData(value: Instance of 'Response')". How can I extract the body of response from this kind of data type?

You can do it like this:
/// A provider that asynchronously exposes the current user
final userProvider = StreamProvider<User>((_) async* {
// fetch the user
});
/// or example
final userProvider = FutureProvider<User>((_) async {
// fetch the user
});
class UserAuthenticationWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<User> user = ref.watch(userProvider);
return user.when(
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('Oops, something unexpected happened'),
data: (user) => Text('Hello ${user.name}'),
);
}
}
Note AsyncValueX. You can use any method that suits you.

Related

Riverpod FutureProvider - passing result between screens

I'm learning Riverpod provider and stuck on a topic regarding passing values between screens.
As I learnt from Riverpod docs - it delivers a provider that enables values to be accessed globally... and here is my case.
I'm creating a service repo, that contains some methods delivering futures (e.g. network request to verify user):
class VerifyUser {
Future<User> verifyUser(String input) async {
await Future.delayed(const Duration(seconds: 2));
print(input);
if (input == 'Foo') {
print('Foo is fine - VERIFIED');
return User(userVerified: true);
} else {
print('$input is wrong - NOT VERIFIED');
return User(userVerified: false);
}
}
}
Next step is to create providers - I'm using Riverpod autogenerate providers for this, so here are my providers:
part 'providers.g.dart';
#riverpod
VerifyUser verifyUserRepo(VerifyUserRepoRef ref) {
return VerifyUser();
}
#riverpod
Future<User> user(
UserRef ref,
String input
) {
return ref
.watch(verifyUserRepoProvider)
.verifyUser(input);
}
and there is a simple User model for this:
class User {
bool userVerified;
User({required this.userVerified});
}
I'm creating a wrapper, that should take the user to Homescreen, when a user is verified, or take the user to authenticate screen when a user is not verified.
class Wrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
String user = '';
final userFromProvider = ref.watch(userProvider(user));
if (user == 'verified') {
print(userFromProvider);
return MyHomePage();
} else {
return Authenticate();
}
}
}
App opens on Authenticate screen because there is no info about the user.
On Authenticate screen I'm getting input and passing it to FutureProvider for verification.
final vUser = ref.watch(userProvider(userInput.value.text));
When I'm pushing to Wrapper and calling provider - I'm not getting the value I initially got from future.
ElevatedButton(
onPressed: () async {
vUser;
if (vUser.value?.userVerified == true) {
print('going2wrapper');
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Wrapper()));
}},
child: const Text('Verify User'))
Inside Wrapper it seems that this is only thing that I can do:
String user = '';
final userFromProvider = ref.watch(userProvider(user));
But it makes me call the provider with a new value... and causing unsuccessful verification and I cannot proceed to homescreen.
As a workaround, I see that I can pass the named argument to Wrapper, but I want to use the provider for it... is it possible?
I hope that there is a solution to this.
Thx!
David

Flutter GetX state management initial null value

This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :

StreamProvider not rebuilding the widget tree when AsyncValue updates

I am to listening firebase AuthStateChanges stream and provide the stream with streamProvider to change the view based on the stream value. And I did this:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream get currentUser => _auth.authStateChanges();
}
final userStream = StreamProvider.autoDispose((ref) => AuthService().currentUser);
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final user = watch(userStream);
print('AutenticationWrapper build method got called');
return user.when(
data: (data) {
if (data?.uid == null) {
print('I am currently Logged out ๐Ÿ˜”');
return LogInPage();
} else {
print('I am logged in user๐Ÿ˜');
return HomePage();
}
},
loading: () => CircularProgressIndicator(),
error: (e, s) => Text('Oops'),
);
}
}
I was expecting to have LogIn page to be rendered when the AsyncValue of the streamProvider gets changed. The above code didn't work as expected; in fact, it prints the message but it doesn't return the Widget it is supposed to return. However, when I hot restart the app it will render the correct Widget based on the stream value.
Why doesn't the build method re-render when this: final user = watch(userStream); receives an update?
I think you should probably watch(userStream.stream) to be notified when the stream itself updates. Haven't played much with StreamProvider though, so I could be wrong.

Is there a way for multiple FutureBuilders to use the same future from a ChangeNotifier?

I have a class (that extends ChangeNotifier - Provider package) that has a function that returns a Future. What I'm trying to do is have it so that multiple futureBuilders in my UI code can receive values from this function but without having to call that function once per FutureBuilder.
However, I the function itself gets run again and again with every FutureBuilder I use. I know there must be a way to expose the Future itself through the Provider package but I can't seem to figure out how.
Here is the class that extends ChangeNotifier and has the future in it:
class ApiService extends ChangeNotifier {
CurrencyTicker _data;
CurrencyTicker get getdata => _data;
set setdata(CurrencyTicker data) {
_data = data;
}
Future<CurrencyTicker> fetchBaseData() async {
final response =
await http.get(API_URL_HERE); // url removed for stackoverflow
if (response.statusCode == 200) {
print('1 call logged');
setdata = CurrencyTicker.fromJson(json.decode(response.body));
return CurrencyTicker.fromJson(json.decode(response.body));
} else {
throw Exception('Request failed: ' + response.statusCode.toString());
}
}
}
Here is the UI code (it's just a FutureBuilder):
class MyBody extends StatelessWidget {
const MyBody({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final provider = Provider.of<ApiService>(context);
return Center(
child: FutureBuilder(
future: provider.fetchBaseData(),
initialData: CurrencyTicker().price,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(snapshot.data.company),
],
);
} else {
return LinearProgressIndicator();
}
},
),
);
}
}
I haven't included the MultiProvider thats at the "top level" in the widget tree since I don't see why I'd have to. I haven't included the Model for the CurrencyTicker class. I can provide both of these if necessary.
Would appreciate any input at all here
You don't want to do the HTTP call directly inside the build method of your consumers.
Instead of exposing a method on your ChangeNotifier, you should expose a property:
class MyNotifier with ChangeNotifier {
Future<Foo> foo;
}
That foo is a variable that stores the result of your last fetchData call.
Depending on your needs, you can then:
call fetchData in the constructor, if immediately needed
class MyNotifier with ChangeNotifier {
MyNotifier() {
foo = fetchData();
}
Future<Foo> foo;
}
lazy load it using a custom getter:
class MyNotifier with ChangeNotifier {
Future<Foo> _foo;
Future<Foo> get foo => _foo ??= fetchData();
}

Flutter: How to make a sequence of http requests on a widget before build method

I have 3 classes: Users, Posts and Comments. User has many Posts and
Posts has many Comments.
I want that all data to be fetched before the widget's build method is called.
I tryed to use initState() to do this:
class FetchDataExample extends StatefulWidget {
final User _user;
FetchDataExample(this._user);
#override
_State createState() => _State(_user);
}
class _State extends State<FetchDataExample> {
final User _user;
_State(this._user);
#override
void initState() {
_user.setPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
print(this._user.posts[0]);
return Container(
);
}
}
In User class I have:
void setPosts() async {
String url = 'https://jsonplaceholder.typicode.com/posts?userId=' + this.id.toString();
var request = Requester.get(url); // Returns a Future<Response>
await request.then((value) => this.posts = Post.jsonToPosts(json.decode(value.body)));
this.posts.forEach((post) => post.setComments());
print(this.posts[0]);
}
The 'setComments()' has the same logic.
I have two prints:
Inside build that returns null;
Inside setPosts the returns Instance of 'Post';
So, by the time that Build method is called in the widget, the initState has not finished yet.
I need it be finished, does anyone know how can I do that?
You can use a FutureBuilder to build a widget by using latest result from a future.
And also you can combile multiple futures into a single one using Future.wait method.
Here is a sample code:
_getPageData() async {
var _combinedFutures = await Future.wait([setPosts, setComments]);
//do stuff with data
}
...
#override
Widget build(BuildContext context) {
return FutureBuilder(
future:_getPageData(),
builder: (context, snapshot) {
return Container();
}),
);
});