How to save data from API in device Flutter - flutter

In my application a lot of data received from the api, how to save data on device when app starts, and take this data from device and take data from the device without making an api call every time.
I can show on the example of my futureBuilder, every time when I go to UserPage, my user data is loaded, how can I save it?
late Future<User> userFuture = getUser();
static Future<User> getUser() async {
var url = '${Constants.API_URL_DOMAIN}action=user_profile&token=${Constants.USER_TOKEN}';
print(Constants.USER_TOKEN);
final response = await http.get(Uri.parse(url));
final body = jsonDecode(response.body);
return User.fromJson(body['data']);
}
FutureBuilder<User>(
future: userFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return buildProfileShimmer();
// Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
final user = snapshot.data!;
return buildUser(user);
} else {
return Text("No widget to build");
}
},
)

Related

StreamBuilder doesn't updates UI when Firestore data changes

My goal:
I want to retrieve a list of documents from the Firebase Firestore using a Stream to update the interface in real time when the Firestore data changes.
The problem:
I am able to download the data from Firestore with the structure I need, but when I make changes in firestore the interface does not update in real time. When I reload the page, it updates, but that is not the behavior I need.
This is the Stream I have created:
Stream<DayModel> getInstances(String selectedDay, String userUid) async* {
DayModel retval = DayModel();
List<InstanceModel> instances = [];
int index = 0;
try {
final QuerySnapshot<Map<String, dynamic>> querySnapshot =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.where("instanceUsersUid", arrayContains: userUid)
.get();
instances = querySnapshot.docs
.map((instance) => InstanceModel.fromSnapshot(instance))
.toList();
for (InstanceModel instance in instances) {
final DocumentSnapshot<Map<String, dynamic>> instanceQuery =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.doc(instance.uid)
.get();
instance = InstanceModel.fromMap(instanceQuery);
instances[index] = instance;
index++;
}
retval.instances = instances;
yield retval;
} on Exception catch (e) {
print(e);
}
}
StreamBuilder code:
body: StreamBuilder<DayModel>(
stream:
OurDatabase().getInstances(selectedDay, _currentUser!.uid!),
builder:
(BuildContext context, AsyncSnapshot<DayModel> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: snapshot.data!.instances!.isNotEmpty
? Text(snapshot.data!.instances![0].uid!)
: Text('No tienes instancias!'),
);
})
Maybe it's because I'm not returning the Stream with a QuerySnapshot?
I have read in other similar posts that it could be a problem with the keys, but I have tried several different combinations and it has not worked.
Do you have any idea what could be happening?
Thank you for your time.

Trying to handle exception in a Future<String?> with no success in a `FutureBuilder` context

I have to fetch ta video URL store in FireStore before displaying the video itself.
I used a FutureBuilderin my build to do so:
if (ad.videoUrl != null)
FutureBuilder(
future: Video.videoUrl("blabla"), //ad.videoUrl!),
builder: (context, snapshot) {
if (snapshot.hasData) {
final url = snapshot.data;
return Text(url ?? "No video found");
} else {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Text("Searching the video on the cloud"),
CircularProgressIndicator()
]);
}
}),
The future itself is straightforward:
/// Fetches the video url from the video [name]
static Future<String?> videoUrl(String name) {
final videoBucket =
FirebaseStorage.instanceFor(bucket: Globals.storageBucketUrl);
final videoBucketRef = videoBucket.ref();
final item = videoBucketRef.child(name);
return item.getDownloadURL();
}
And like this, I've got what I want. Perfect.
But now, I want to handle the exception within the future so that it returns null when any occur.
I've updated my code as follows:
/// Fetches the video url from the video [name]
static Future<String?> videoUrl(String name) async {
final videoBucket =
FirebaseStorage.instanceFor(bucket: Globals.storageBucketUrl);
final videoBucketRef = videoBucket.ref();
try {
final item = videoBucketRef.child(name);
return await item.getDownloadURL();
} catch (e) {
return null;
}
Though the exception is caught as expected (when I fetch a video name that does not exist), it looks like the future never returns a value (neither null or the URL when it exists.
Therefore what's wrong in my code?
Note: Btw, I have also tried this without success when the exception occurs, though it is ok for an existing video name.
/// Fetches the video url from the video [name]
static Future<String?> videoUrl(String name) async {
final videoBucket =
FirebaseStorage.instanceFor(bucket: Globals.storageBucketUrl);
final videoBucketRef = videoBucket.ref();
try {
final item = videoBucketRef.child(name);
final url = await item.getDownloadURL();
return Future<String?>.value(name);
} catch (e) {
return Future<String?>.value(null);
}
instead of returning null, you can throw an exception like this:
catch (e) {
throw Exception("some error here");
}
inside the FutureBuilder you can use the hasError to show some widget based on it:
FutureBuilder(
future: Video.videoUrl("blabla"), //ad.videoUrl!),
builder: (context, snapshot) {
if (snapshot.hasData) {
final url = snapshot.data;
return Text(url ?? "No video found");
} else if(snapshot.hasError) {
return Text("error ${snapshot.error}");
} else{
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Text("Searching the video on the cloud"),
CircularProgressIndicator()
]);
}
}),

need help in API integration

I hope you all are well.
I got a problem i am learning API integration in flutter now a days the problem I am facing is i can't get data here is the code below:
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getuser(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return CircularProgressIndicator();
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index].title),
);
},
);
}
},
));
}
}
it is only showing me circular indicator i am using API 'https://jsonplaceholder.typicode.com/posts'.
I tried to check if the API is working so i check it by passing a hello in list tile and getting the hello by the length of API given in item count and actually that showed me output according to length please help me out so that i can move forward.
Thank You.
Here is the function also:
import 'package:apiintegration/model/user_model.dart';
import 'package:http/http.dart' as http;
getuser() async {
var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
var response = await http.get(url);
var responsedata = jsonDecode(response.body);
return UserModel.fromJson(responsedata);```
You should continue step by step.
As you said if you have success response and not null data, you might have parsing problem.
You should go to your url => https://jsonplaceholder.typicode.com/posts again and copy the json data.
Open https://app.quicktype.io/ site and paste your json data here
to create related parsing methods.
Make http request again. If you parse the json data correctly check out getUser method in view file.
When you get response, be sure that you re-draw(setState etc.) the ui
for displaying parsed json data.
If everything works well you should handle all the states
that you can have from FutureBuilder such as:
if(snapshot.connectionState == ConnectionState.none) {...}
else if(snapshot.connectionState == ConnectionState.waiting) {...}
else if(snapshot.connectionState == ConnectionState.done) {
if(snapshot.hasError) {...}
if(snapshot.hasData) {...}
}
problem is here
return UserModel.fromJson(responsedata);```
it should be userModelFromJson(responsedata);
Example Model:
import 'dart:convert';
DefaultModel defaultModelFromJson(String str) =>
DefaultModel.fromJson(json.decode(str));
String defaultModelToJson(DefaultModel data) => json.encode(data.toJson());
class DefaultModel {
DefaultModel({
this.response,
this.data,
});
String? response;
String? data;
factory DefaultModel.fromJson(Map<String, dynamic> json) => DefaultModel(
response: json["response"],
data: json["data"],
);
Map<String, dynamic> toJson() => {
"response": response,
"data": data,
};
}

Future builder returns null although my list is not empty

I have this future builder which loads a list of movies in my provider class. Whenever I reload my screen, the movies do not get returned. Below is the future builder
FutureBuilder(
future: movieData.getTrendingMovies(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData) {
return Swiper(
itemBuilder: (BuildContext context, i) {
return ChangeNotifierProvider(
create: (context) => Movie(),
child: MovieContainer(
imageUrl: movieData.movies[i].imageUrl,
id: movieData.movies[i].id,
rate: movieData.movies[i].rate,
title: movieData.movies[i].title,
),
);
},
itemCount: movieData.movies.length,
viewportFraction: 0.25,
scale: 0.4,
);
} else {
return Text(snapshot.error.toString()); // it returns null on the screen
}
}),
Also in my homescreen where I display my movies, after the build method, I create a listener(moviesData) to listen to all changes in the movies provider.
final movieData = Provider.of<Movies>(context, listen: false);
Below is also the methos which fetches the movies from a restfulAPI using http get request
Future<void> getTrendingMovies() async {
List<String> movieTitles = [];
List<String> movieImageUrls = [];
List<String> movieDescriptions = [];
List<String> movieReleaseDates = [];
List<String> movieRates = [];
List<String> movieIds = [];
const _apiKey = '******************************';
const url =
'https://api.themoviedb.org/3/trending/all/week?api_key=$_apiKey';
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode >= 400) {
print(response.statusCode);
return;
}
final extractedData = json.decode(response.body);
List moviesList = extractedData['results'] as List;
List<Movie> loadedMovies = [];
for (int i = 0; i < moviesList.length; i++) {
String movieTitle = moviesList[i]['original_title'] ?? '';
String? movieImage =
'https://image.tmdb.org/t/p/w400${moviesList[i]['poster_path']}'; //results[0].poster_path
String movieDescription =
moviesList[i]['overview'] ?? ''; //results[0].overview
String movieReleaseDate = moviesList[i]['release_date'] ?? '';
String? movieRate = moviesList[i]['vote_average'].toString();
String? movieId = moviesList[i]['id'].toString();
movieTitles.add(movieTitle);
movieImageUrls.add(movieImage);
movieDescriptions.add(movieDescription);
movieReleaseDates.add(movieReleaseDate);
movieRates.add(movieRate);
movieIds.add(movieId);
loadedMovies.add(
Movie(
id: movieIds[i],
title: movieTitles[i],
imageUrl: movieImageUrls[i],
description: movieDescriptions[i],
rate: double.parse(movieRates[i]),
releaseDate: movieReleaseDates[i],
),
);
}
_movies = loadedMovies;
notifyListeners();
//print(_movies.last.title); //This prints the name of the last movie perfectly....This gets called unlimited times whenever I set the listen of the **moviesData** to true
} catch (error) {
print(error);
}
}
There's a couple of things to unpack here.
Instead of a ChangeNotifierProvider, I believe you should use a Consumer widget that listens to your Movies provided service when you call the notifyListeners call, so make it Consumer<Movie>.
You can still call it using the Provider.of above for the sake of making the async call via the FutureBuilder, but I believe because you're not returning anything out of the getTrendingMovies and is just a Future<void> and you're querying the snapshot.hasData, well there is no data coming through the snapshot. Maybe instead you should call snapshot.connectionState == ConnectionState.done as opposed to querying for whether it has data.
Make sure that the response.body is truly returning a JSON value, but I believe your issue is in one of the points above.

Flutter FutureBuilder always return null

FutureBuilder keep returning null even when the http request successfully fetch the data from api.
I use json_serializable and freezed package in my data model, is this error related to them or it is purely coming from flutter/dart?
Http request
Future<VitalSignResponse> getLatestVitalSign(String medicalNo) async {
final String url = Api.baseUrl +
'VitalSignByMedicalNoLatest?AccessKey=${Api.accessKey}&MedicalNo=$medicalNo';
Response res = await Dio().get(url);
print('api res: ${res.data}'); // 'api res: the correct data from api'
print('serialize: ${VitalSignResponse.fromJson(json.decode(res.data))}'); // print out nothing
return VitalSignResponse.fromJson(json.decode(res.data));
}
data model
#freezed
abstract class VitalSignResponse with _$VitalSignResponse {
#JsonSerializable(explicitToJson: true)
const factory VitalSignResponse(
final String status,
final String errorCode,
final List<VitalSign> data,
) = _VitalSignResponse;
factory VitalSignResponse.fromJson(Map<String, dynamic> json) =>
_$VitalSignResponseFromJson(json);
}
future builder logic
FutureBuilder(
future: service.getLatestVitalSign(patientBloc.patient.medicalNo),
builder: (
BuildContext context,
AsyncSnapshot snapshot,
) {
print(snapshot.data); // null
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
if (snapshot.hasData) {
// show data
}
return Container();
},
),
I simply forgot to run my project with debugging
i leave the question here as a personal reminder and for new developer out there to not making the same mistake i did. And for my friends to laugh at me haha