Flutter - Riverpod. Save response from http in a provider - flutter

I new to Riverpod, and I have a basic question.
On the start up of my app, I make an http to load a preload. This returns a Json response, with basic stings and lists of string, data that is used around the app.
I make the request like this:
class PreLoadApi {
getPreloadList() {
http.get(url,
headers: {HttpHeaders.contentTypeHeader: "application/json", HttpHeaders.authorizationHeader: "Bearer $token"}
).then((http.Response response) {
int statusCode = response.statusCode;
print(statusCode);
log("2124: PreLoad. response.body:${response.body}");
var data = json.decode(response.body);
String name = data['name']
*** Here I want to save the string "name" to a provider. ***
}
Now I want to save this String name in a provider. I would like to make a provider like this:
final nameProvider = StateProvider((ref)=> name);
I want this, so that this string will be available all over the app.
However, providers have to been defined top level, so I'm not sure how to get the data there?
Meaning, in the above http request I make, in the .then function which is called on the result of the request, I want to store there the value into a provider. How to I create a provider there? Since the provider is global, I don't know how to save the result data String into the provider
I hope the question is clear, and thanks for the help!

To read a provider, you need a WidgetRef. You can pass it on getPreloadList . In this case it will be
final nameProvider = StateProvider.autoDispose((ref) => "");
class PreLoadApi {
getPreloadList(WidgetRef ref) {
//Future.delayed will be your http.get
Future.delayed(Duration(seconds: 3)).then((value) {
ref.read(nameProvider.notifier).state = "new Value";
});
}
}
To have a widgetRef you can wrap the widget with Consumer
return Consumer(
builder: (context, ref, child) => Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
PreLoadApi api = PreLoadApi();
api.getPreloadList(ref);
},
),
body: Text(ref.watch(nameProvider)),
There are others way handling this like getting text from future instead of passing WidgetRef. Also check StateNotifierProvider, ChangeNotifierProvider.
You can start from riverpod.dev

Because your "name" is valid only after a Future completes, the most likely holder for the value is a FutureProvider. Nicely, this will cache the value, as long as you don't make the provider autoDispose.

Related

How to invoke updating streamProvider if i send a new request data in parameter create?

I have StreamProvider whiсh get data from FireStore where i passing on id of document that
i want to retrieve.
This is StreamProvider in root of widgets:
StreamProvider<List<TheExercise>?>(create: (_) => DatabaseService(programId: context.watch<DataChangeNotifier>().getData).exercises, initialData: null, ),
In "context.watch().getData" i pass id that will change depends on user activity, but i can't figure out how to rebuild streamProvider when it's get a new id.
Now stream is building one time when i listen to it.
final _exercises = Provider.of<List<TheExercise>?>(context)?? [];
Here is code in DatabaseService class:
CollectionReference exercisesCollection(){
final CollectionReference _exercisesCollection = FirebaseFirestore.instance.collection('userData').doc(_auth.currentUser!.uid.toString()).collection('programs')
.doc(programId).collection('exercises');
return _exercisesCollection;
}
Stream<List<TheExercise>> get exercises {
return exercisesCollection().snapshots()
.map((listOfExercise));
DataChangeNotifier class code:
class DataChangeNotifier with ChangeNotifier {String _dataIdOfExercise = '';String get getData => _dataIdOfExercise;void changeIdOfExercise(String dataIdOfExercise) {
_dataIdOfExercise = dataIdOfExercise;
notifyListeners();} }
I solved it replase StreamProvider on StreamProvider.value.
As i understood it takes a value every time depens on link in parameter "value".
StreamProvider<List<TheExercise>?>.value(value: DatabaseService(programId: context.watch<DataChangeNotifier>().getData).exercises, initialData: null),
But will appreciate any answers for additional knowledge.

Getting StateError (Bad state: Too many elements) while streaming in Dart/Flutter

I'm using Multiprovider to provide my custom GameLobby class like that:
var game_lobby = GameLobby(user: user);
return MultiProvider(
providers: [
StreamProvider<GameState>(create: (_) => game_lobby.gameStateStream,),
Provider<GameLobby>(create: (_) => game_lobby,),
],
child: GameRoomScreen()
);
Later, in GameLobby class I made a call to firebase.firestore to create a new collection
await gameRef.doc(user.uid).set({
'lobbyName': 'Lobimiz',
'code' : generateNewRoomCode(),
'date': Timestamp.now(),
'players' : [user.uid]
})
.then((value) => {
gameRef.doc(user.uid).snapshots().listen((event) {
Map<String, dynamic> firestoreInfo = event.data();
players = [...firestoreInfo['players']];
print(event.data().toString());
gameState = GameState.InLobby; /* GAMESTATE SETTER IS CALLED */
})
})
// ignore: missing_return
.onError((error, stackTrace) {print(error.toString());});
This code usually works for the 1st time. When I manually insert an entry on firebase for testing purpose,
gameRef.doc(user.uid).snapshots().listen() is triggered again since entry is changed and I'm listening to it.
This time gameState attribute is change and gameState is streamed in setter method like:
set gameState(GameState state){
this._gameState = state;
this._streamController.sink.add(this._gameState);
}
I wanted to add new gameState to stream and rebuild something according to new state and playerlist. But now I'm getting
StateError (Bad state: Too many elements)
Which is defined and throwned as in here:
https://api.flutter.dev/flutter/dart-async/Stream/single.html
// This is the second element we get.
try { throw
IterableElementError.tooMany();
What is the problem here? Doesn't streams allow more than one sink element at a time?
Stream.single is the async equivalent of List.single, which takes a List and returns the one and only element in the list:
['hello'].single // returns the String 'hello'
['hello', 'world'].single // throws StateError
[].single // also throws StateError
Stream.single behaves similarly: if you call it on a stream with no elements, it throws, and if you call it on a stream with 2 or more elements, it also throws.
You don't include the code that actually calls single, so it's hard to know what you're using it for, but a common mistake is to think that single gives you the "current value" of the Stream.
Unfortunately, Dart Streams don't really have a "current value". You have a couple of options if this is what you're after:
Use a StreamBuilder:
StreamBuilder(
stream: stream, // make sure you create this outside build(), e.g. in initState()
builder: (context, snapshot) {
// snapshot.data gives you the "current value"
}
)
If you want to do a similar thing in a flutter-independent way, you can use the rxdart package and teh BehaviorSubject class:
BehaviorSubject subject = streamData(); // a special type of stream provided by rxdart
await doSomeAsyncStuff();
subject.value // the last value added to this subject, or throws if nothing was added

How do I use Futures in Futures in Flutter?

Soo, the title might be a bit confusing but let me clear that up right now.
I have a class called UserController which has a method called updateUserData. It gets a Map<String, dynamic> and updates the given attributes of a user with whatever is given in the value of the map.
What I wanted to do is: Send a patch request to the server, wait for the server to return a changed user object, write that to some local variable and return either the value or the error to whoever called that method (which in my case is a GUI class).
The current method as it is:
Future<User> updateUserData(Map<String, dynamic> changes) async {
return await http.patch(
"url",
headers: {HttpHeaders.authorizationHeader: "token"},
body: changesMap
).then((newUserObject) => {
currentUser = newUserObject;
//return new user object for display
}); //error from server gets forwarded to GUI.
}
Sadly this doesn't work at all. Seems like Flutter/dart doesn't know what to return there (it gives me a return_of_invalid_type_from_closure error).
I hope it's clear what my goal was. I want to use a "then" clause in this method but then still return a future which either contains the user I get from the server or the error I get.
How do I do that? I looked up so many Future tutorials so far and none used something similar.
You never need to use async/await with then. In your case, the simplest thing to do is await the response of your request, and then, put it to your local variable.
Then you just need to return the value.
Future<User> updateUserData(Map<String, dynamic> changes) async {
final response = await http.patch(
"url",
headers: {HttpHeaders.authorizationHeader: "token"},
body: changesMap,
);
// You need to parse the response to get your User object.
final responseJson = json.decode(response.body);
newUserObject = User.fromJson(responseJson);
currentUser = newUserObject;
return newUserObject;
}
If you need to decode your Map to a Class, you can see the answer given here

How display data which comes from async function?

I use api for get information which need to be display
Future <String> Get_Amount_Jackpot() async {
// SERVER LOGIN API URL
var url2 = 'https://www.easytrafic.fr/game_app/get_jackpot_lotto.php';
// Starting Web API Call.
var response2 = await http.get(url2,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
Map<String, dynamic> jsondata2 = json.decode(response2.body);
return jsondata2["value"];
}
I call it here :
void initState() {
ListLotto = Grille_display();
jackpot = Get_Amount_Jackpot();
super.initState();;
}
How i can display the value "jackpot" which is a simple number on mobile screen. Note i use futurebuilder for another api request, this is why it is complicated. Normally i use futurebuilder for display async data but there i need 2 différents api request so 2 futureBuilder ??? Is it possible and how do that ?
You can use then to get the returned value from the function.
Get_Amount_Jackpot().then((value){
//value is what is returned from the function
jackpot = value;
});
I'm not sure if you can use uppercase letter as the start of a function name, but i copied your function name for my answer. Hope this helps.

Flutter http Maintain PHP session

I'm new to flutter. Basically I'm using code Igniter framework for my web application. I created REST API for my web app, after user login using API all the methods check for the session_id if it exists then it proceeds, and if it doesn't then it gives
{ ['status'] = false, ['message'] = 'unauthorized access' }
I'm creating app with flutter, when i use the http method of flutter it changes session on each request. I mean, it doesn't maintain the session. I think it destroys and creates new connection each time. Here is thr class method which i use for api calls get and post request.
class ApiCall {
static Map data;
static List keys;
static Future<Map> getData(url) async {
http.Response response = await http.get(url);
Map body = JSON.decode(response.body);
data = body;
return body;
}
static Future postData(url, data) async {
Map result;
http.Response response = await http.post(url, body: data).then((response) {
result = JSON.decode(response.body);
}).catchError((error) => print(error.toString()));
data = result;
keys = result.keys.toList();
return result;
}
I want to make API request and then store session_id,
And is it possible to maintain session on the server so i can manage authentication on the web app it self.?
HTTP is a stateless protocol, so servers need some way to identify clients on the second, third and subsequent requests they make to the server. In your case you might authenticate using the first request, so you want the server to remember you on subsequent requests, so that it knows you are already authenticated. A common way to do this is with cookies.
Igniter sends a cookie with the session id. You need to gather this from each response and send it back in the next request. (Servers sometimes change the session id (to reduce things like clickjacking that we don't need to consider yet), so you need to keep extracting the cookie from every response.)
The cookie arrives as an HTTP response header called set-cookie (there may be more than one, though hopefully not for simplicity). To send the cookie back you add a HTTP request header to your subsequent requests called cookie, copying across some of the information you extracted from the set-cookie header.
Hopefully, Igniter only sends one set-cookie header, but for debugging purposes you may find it useful to print them all by using response.headers.forEach((a, b) => print('$a: $b'));. You should find Set-Cookie: somename=abcdef; optional stuff. We need to extract the string up to, but excluding the ;, i.e. somename=abcdef
On the next, and subsequent requests, add a request header to your next request of {'Cookie': 'somename=abcdef'}, by changing your post command to:
http.post(url, body: data, headers:{'Cookie': cookie})
Incidentally, I think you have a mismatch of awaits and thens in your code above. Generally, you don't want statics in classes, if they should be top level functions instead. Instead you could create a cookie aware class like:
class Session {
Map<String, String> headers = {};
Future<Map> get(String url) async {
http.Response response = await http.get(url, headers: headers);
updateCookie(response);
return json.decode(response.body);
}
Future<Map> post(String url, dynamic data) async {
http.Response response = await http.post(url, body: data, headers: headers);
updateCookie(response);
return json.decode(response.body);
}
void updateCookie(http.Response response) {
String rawCookie = response.headers['set-cookie'];
if (rawCookie != null) {
int index = rawCookie.indexOf(';');
headers['cookie'] =
(index == -1) ? rawCookie : rawCookie.substring(0, index);
}
}
}