How to cache the response of API calls in Flutter? - flutter

I am working in Flutter App to get the items from API. I want to cache the API response for 12 hours. Every 12 hours the response will be changed. Once the 12 hours completed then I need to fetch it from Internet. I used the below code to get it from internet.
Future<List<Playlist>> fetchPlaylistByChannelId({String channelId}) async {
Map<String, String> parameters = {
'part': 'snippet,contentDetails',
'channelId': channelId,
'maxResults': '10',
'key': API_KEY,
};
Uri uri = Uri.https(
_baseUrl,
'/youtube/v3/playlists',
parameters,
);
Map<String, String> headers = {
HttpHeaders.contentTypeHeader: 'application/json',
};
// Get Playlist details
var response = await http.get(uri, headers: headers);
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<dynamic> playListJson = data['items'];
// Fetch all play list
List<Playlist> playLists = [];
playListJson.forEach(
(json) => playLists.add(
Playlist.fromMap(
json["id"],
json["snippet"],
json["contentDetails"],
),
),
);
return playLists;
} else {
throw json.decode(response.body)['error']['message'];
} }
Please help me out this.

Include flutter_cache_manager in pubspec.yaml.
Now define a cache manager
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
// Custom Implementation of CacheManager
// by extending the BaseCacheManager abstract class
class MyCacheManager extends BaseCacheManager {
static const key = "customCache";
static MyCacheManager _instance;
// singleton implementation
// for the custom cache manager
factory MyCacheManager() {
if (_instance == null) {
_instance = new MyCacheManager._();
}
return _instance;
}
// pass the default setting values to the base class
// link the custom handler to handle HTTP calls
// via the custom cache manager
MyCacheManager._()
: super(key,
maxAgeCacheObject: Duration(hours: 12),
maxNrOfCacheObjects: 200,
fileFetcher: _myHttpGetter);
#override
Future<String> getFilePath() async {
var directory = await getTemporaryDirectory();
return path.join(directory.path, key);
}
static Future<FileFetcherResponse> _myHttpGetter(String url,
{Map<String, String> headers}) async {
HttpFileFetcherResponse response;
// Do things with headers, the url or whatever.
try {
var res = await http.get(url, headers: headers);
// add a custom response header
// to regulate the caching time
// when the server doesn't provide cache-control
res.headers.addAll({'cache-control': 'private, max-age=120'});
response = HttpFileFetcherResponse(res);
} on SocketException {
print('No internet connection');
}
return response;
}
}
Now use
class HttpProvider {
Future<Response> getData(String url, Map<String, String> headers) async {
var file = await MyCacheManager().getSingleFile(url, headers: headers);
if (file != null && await file.exists()) {
var res = await file.readAsString();
return Response(res, 200);
}
return Response(null, 404);
}
}
Details at https://referbruv.com/blog/posts/caching-get-request-calls-using-flutter-cache-manager and https://proandroiddev.com/flutter-lazy-loading-data-from-network-with-caching-b7486de57f11
UPDATE: flutter_cache_manager 2.0.0
There is no longer a need to extend on BaseCacheManager, you can directly call the constructor. The BaseCacheManager is now only an interface. CacheManager is the implementation you can use directly.
check here

Another way of caching is by using hive a No-SQL database it is faster to retrieve documents and is easy to use. And when users come online just refresh the data in hive
For more details check:https://github.com/shashiben/Anime-details to know how to cache using hive

Related

How to properly make a api request in flutter?

Referring to this article
https://medium.com/solidmvp-africa/making-your-api-calls-in-flutter-the-right-way-f0a03e35b4b1
I was trying to call API from a flutter app. But to make it the right way, I was looking for a complete example and came here. My question is why do I need to create an ApiBaseHelper class then RepositoryClass then all other formalities to call an API. Why can't I use FutureBuilder and a simple async function associated with the API like this:
class Networking {
static const BASE_URL = 'https://example.com';
static Future<dynamic> getProductById({
required String? token,
required String? productId,
}) async {
final url = Uri.parse('$BASE_URL/products/$productId');
final accessToken = 'Bearer $token';
Map<String, String> requestHeaders = {
'Authorization': accessToken,
'Content-Type': 'application/json'
};
try {
final response = await http.get(
url,
headers: requestHeaders,
);
if (response.statusCode != 200) {
throw Exception('Error fetching data.');
}
final responseJSON = json.decode(response.body);
if (responseJSON['error'] != null) {
return throw Exception(responseJSON['error']);
}
final product = Product.fromJson(responseJSON);
return product;
} catch (e) {
throw Exception(e.toString());
}
}
}
And then calling it from a FutureBuilder like this:
FutureBuilder(
future: Networking.getProductById(token, id),
builder: (context, snapshot) {
// rest of the code
}
)
Can anyone tell me what is the most convenient and widely used way to call an API?

Flutter how can i set Auth token from flutter secure storage to dio header?

After login i setting user token to my user Secure storage. Like :
Future<AuthResponseModel?> login(AuthRequstModel model) async {
try {
Response response = await _dio.post(loginPath, data: model);
if (response.statusCode == 200) {
final AuthResponseModel authResponseModel = AuthResponseModel.fromJson(response.data);
if (authResponseModel.success!) {
await UserSecureStorage.setField("token", authResponseModel.token);
}
return AuthResponseModel.fromJson(response.data);
}
return null;
} catch (e) {
return null;
}
}
User Secure Storage =>
class UserSecureStorage {
static const _storage = FlutterSecureStorage();
static Future setField(String key, value) async {
await _storage.write(key: key, value: value);
}
static Future<String?> getField(key) async {
return await _storage.read(key: key);
}
But problem is when i want to make apiservice and when i want to auth token inside header of dio, I cant access it becouse its a future<String?> function. But i cant use await coz its inside of baseoption. Like :
class ApiService {
final _dio = Dio(BaseOptions(headers: {
'authorization': 'Bearer ${UserSecureStorage.getField("token")}', //I cant access here its only giving instance.
}));
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path');
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
What can i do for solve that problem ? I tried use .then(value=>value) after tried get token but didnt work too. Thanks for responses!
I think token is not getting updated because _dio is already intitalized.
Try to request for token when dio request is made like :
class ApiService {
final _dio = Dio();
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path', options: Options(headers: {"authorization": "Bearer ${UserSecureStorage.getField("token")}"}));
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
Use options in get method to add headers for a single request or interceptors for all requests.
I think that it is not an issue easily solvable, I would try with two different methods, you can maintain the token in a state manager such as Provider so you don't have to rely on an async function to retrive it, but this of course add in the code the state manager structure that complicates thing a little.
A bit more naive way to solve this could be to include a async initializator in the ApiService class such this
class ApiService {
late final _dio;
Future<void> init() async {
_dio = Dio(BaseOptions(headers: {
'authorization': 'Bearer ${UserSecureStorage.getField("token")}', //I cant access here its only giving instance.
}));}
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path');
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
And this introduce us a new issue, we have to call init everytime the class ApiService is instantiated, to solve this you could use the package get_it which grants you the possibility to instatiate only once the class and access it from everywhere in your project.
I hope this will help you solve your problem
your are getting instance because UserSecureStorage.getField("token") is future so you can get token when you put await keyword
so try like this
await UserSecureStorage.getField("token")

How to create a post request in flutter - GetConnect

I am creating a flutter app using GetX, for making server connections I am using GetConnect, I successfully integrated the Get Request but I am unable to integrate Post Request
Here is the piece of code:
const _baseUrl = 'https://support.instagram.com/'; // Dummy api url and key
const Map<String, String> _mapHeaders = {
"ISG-API": "ZMWFDK83NMDF7NM5DF23FI0DBUJ"
};
class ApiService extends GetConnect {
Future<TicketReply> submitTicketReply(String ticketId, String tktreply) async {
String apiUrl = '${_baseUrl}/supportreply';
var body = {
'tktid': ticketId,
'tktreply': tktreply,
};
final response = await post(
apiUrl,
body,
headers: _mapHeaders,
);
print('response: ${response.body}');
if (response.statusCode == 200) {
return TicketReply.fromJson(response.body);
} else {
return Future.error(response.statusText!);
}
}
}
Kindly guide me how to make a successful post request using getconnect
You should add the decoder to the request like so
await post(
apiUrl,
body,
headers: _mapHeaders,
decoder: (resbody) => TicketReply.fromJson(resbody),
);

Is there something wrong with the way [Flutter Web] sends a request POST message to the API server?

The open api request I'm trying to use requires an image binary value with content-type of multipart/form-data format.
I know you can't use dart:io in flutter web. I tried to upload an image in multipart/form-data format to the api server in flutter web while looking at several posts.
However, only a message appeared stating that the image could not be recognized.
This is the last thing I tried to create multipart types in flutter web.
import 'package:dio/dio.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
PlatformFile? objFile;
pickImage() async {
var result = await FilePicker.platform.pickFiles(
withReadStream: true,
);
setState(() {
objFile = result!.files.single;
});
uploadImage();
}
uploadImage() async {
FormData formData = FormData.fromMap({'image' : MultipartFile(test!, objFile!.size, filename: objFile!.name)});
Dio dio = new Dio();
var response = await dio.post('API url', data: formData);
}
I additionally used Multipart.form Bytes from http , Multipart.form Bytes from dio . But the result was the same.
The value checked by the request body through the postman interceptor.
content-type=multipart/form-data;bounary=--dio-boundary-1105759322
----dio-boundary-1105759322
content-disposition: form-data; name="image"; filename="test.jpeg"
content-type: application/octet-stream
ÿØÿÛC
%# , #&')*)-0-(0%()(ÿÛC
(((((((((((((((((((((((((((((((((((((((((((((((((((ÿÀŽv"ÿÄÿÄC!1AQaq"‘2¡±#BÁÑR3CðñSbr’á‚Â$&4c“ÿÄÿÄ&!1A2Q"a3BRÿÚ?ù× „É<$/cŸt8D`aú¦Ä#bálŒZVM„ٔʓTL›eOò¢“
èKÇ(p¢‰¥C’ÄÙ‚Ñx²Ù1Jcœ)B›¢$ ¢‚&
‚7› ˜Žp”{&ÊÀÁAî¤Æ
‚nÈ CØÃêOýÒ›§á$sÊ‚r¡ìLÂ…;"éMI½î«gæV<æ6οÙ%_ƒY®}7Òû€¯MŒ&g¹å|µ£ëÐúc\tÚƵƈúÕ]#kQ‹D/Ÿú·cu9«Hà/¢lÚ–êè·¼&Þt
¯H‚&ɶìÛà®iƒh²SöãÔTs[l›/?[s(’˜¨o€¤Û‹*¥AÖ”ðbUgYR’!äJ!M‹™‹«›î©aÉ*ᕨ4p SÉ…¤)‰ì§=‘âJ» oÙGDRåÌy0—²û r ò€·²?Te8±KSTR8ŹDAååþ7)Oˆk)õ²Qk#Ù€Œ ?DÜû&Ä›„ÍÅ”lQjð¡NÑ%HTWP˜²wýÒc(Ÿð¤ð¢S<*6º>ÊaCœ „Ù0
^J(ª%¢ƒFPm‘^u4^èM‘åL…##•0Qÿ ºi…32§ÙC•D¿&Èw’ˆº‘Ü"…”<&ýРwP {p ¸DCd¼&ÿ©#¨ˆ› La~¨p¦„)’÷‚ˆº²æÒ›ªĘ̀Šaá€0‹n <ò¦M“YM„ L«=ÕnæÊlªŽÂƒóc„m‚—È™Uó ªºäªÛ•F†\…}7?¨ªZL`*£è¾ŽÝÌ1¤ÜBúk6­
---------------------------SKIP------------------------------
PTiMÂ!¢(èÊ€YÊÂœ"ÑÂ_T<Ñ5îPp™ð ¨„ôOˤ?¢z\ÂÚ¡½ÐiÊc쨟ÝHŸ¢“3ÝA˜( ‘ÊH›(l€Å¼)Ä‘rEÈ[€‹¬”¼x
W7q?ΣHt®“§¤y\½Ìÿ:ÿÍtÖ§T°AÊÕ\ËZVƒÔPha30%1*¶›Ž!7è¥|f›„îÕQ±„9N6åW,¨^Ù8PHN./Ê€îª2ß*{(l¡™šOU¢Ôå3œ*ꜨŠ‹“3¼$«B*ÌŒS„+EÒ‘Ý VHpV±`²³ó€µgܪ‚#“Ü)À!NPCƒÝIÅԛ–”xý”²™# ?U‚‹n€å!Œ¦&é*ƒ™¨wÄÖØY¢>«}&ü¢×\Ý?ó*9ç%Òº˜#çò H€¥&ꃒ¤(
‚0O8##EÎéÊœ#TÕr‚ºT¹ÈÔ7T“2¢ƒœbÅsuOî¶Ô0>‹ŸT|Gô•Óa®ïšÔÇe¤T
he<,¨[ü¶[…·M#ZOˆjtˤÝE© QÿÙ
----dio-boundary-1105759322--
When I use the MultipartFile.fromFile method used in flutter ios, I got the response normally. So I'm pretty sure there must be some mistake or misinformation in the flutter web setup.
Thanks in advance!
this is how I managed to upload an image to Laravel backend using Flutter Web
import 'dart:io';
import 'package:dio/dio.dart' as dio;
import 'package:file_picker/file_picker.dart';
Future pickupUserImage2() async {
PlatformFile? objFile;
var picked = await FilePicker.platform.pickFiles();
objFile = picked!.files.single;
String? apiUrl = Get.find<MainController>().apiUrl;
String url = '';
if (apiUrl != null) url = apiUrl + 'images';
List<int> list;
list = List<int>.from(objFile.bytes!);
dio.FormData formData = dio.FormData.fromMap({'file': dio.MultipartFile.fromBytes(list, filename: objFile.name)});
DioNetworking _dio = new DioNetworking();
dynamic result = await _dio.postData(
url,
formData,
contentType: 'multipart/form-data',
);
if (result['status'] == true) {
showToast(result['message']);
return ImageModel.Image.fromJson(result['data']);
}
}
I added a file using MultipartFile.fromBytes which has the first parameter as List which I created from this method "List.from(objFile.bytes!);"
Notes :
ImageModel.Image is a model I created to handle the image result
DioNetworking is a class that perform dio requests , I just created it to do the
authentication stuff
import 'package:dio/dio.dart';
class DioNetworking {
Dio _dio = new Dio();
Future postData(
String url,
dynamic data, {
String? contentType,
}) async {
try {
Response response = await _dio.post(url,
data: data,
options: Options(headers: {
'content-type': contentType != null ? contentType : 'application/json',
'Accept': 'application/json',
// 'Authorization': 'Bearer ${token ?? ''}'
// other headers
}));
if (response.statusCode == 200) {
dynamic data = response.data;
return data;
} else {
print(response.statusCode);
}
} on DioError catch (e) {
print(e);
if (e.type == DioErrorType.connectTimeout) {
return {'status': 'Connect Timed Out'};
}
if (e.type == DioErrorType.receiveTimeout) {
return {'status': 'Receive Timed Out'};
}
if (e.type == DioErrorType.response) {
print(e.response!.data);
print(e.response!.headers);
}
}
}
}

add multiple headers to http response in flutter

i am beginner in flutter , i started to call an api from rapid api for soccer results , it's required to add many headers to call this api , when i put these headers , i had an error in syntax
here what i have tried , nothing worked
getResults() async {
http.Response response = await http.get(
'https://api-football-v1.p.rapidapi.com/v2/fixtures/league/2',
headers: ({'x-rapidapi-key': 'e8ece1dbcdmshd1d6f778c1397c5p113b22jsn8361690f93a2'},
{'x-rapidapi-host': 'api-football-v1.p.rapidapi.com'},
{'useQueryString': true}));
I am trying to find the right syntax to make these headers works with my request
import 'package:wnetworking/wnetworking.dart';
class RapidAPI {
static const _base = 'https://api-football-v1.p.rapidapi.com/v2';
static const _apiKey = 'e8ece1dbcdmshd1d6f778c1397c5p113b22jsn8361690f93a2';
static Future<void> fetchFixturesLeague2() async {
final url = '$_base/fixtures/league/2';
print('Start fetching...');
await HttpReqService.getJson<Map<String, dynamic>>(url, headers: {
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'x-rapidapi-key': _apiKey,
})
.then((response) {
if (response != null) {
print('${response['api']['fixtures'].length} items');
}
})
.whenComplete(() => print('Fetching done.'));
}
}
void main(List<String> args) async {
await RapidAPI.fetchFixturesLeague2();
print('Job done!');
}
Result:
Start fetching...
380 items
Fetching done.
Job done!
Put them in a single map:
headers: (
{'x-rapidapi-key': 'e8ece1dbcdmshd1d6f778c1397c5p113b22jsn8361690f93a2',
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'useQueryString': true}
)