flutter return future list in a var to use outside the loop - flutter

Hello I'm trying to recuperate the list value of a database.
i can but what i want is to export the result in a var so i can use in all my code just by calling "print(myList);"
this is my code :
static const URL =
'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(
URL,
));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw <Employee>[];
}
} catch (e) {
return <Employee>[];
}
}
and my classe Employee
class Employee {
String id;
String firstName;
String lastName;
Employee({required this.id, required this.firstName, required this.lastName});
factory Employee.fromJson(Map<String, dynamic> json) {
return Employee(
id: json['id'] as String,
firstName: json['lat'] as String,
lastName: json['lng'] as String,
);
}
}
can i have help please ?

There are two ways to access async data in most modern languages, including dart, they are:
1. By providing a callback then
2. By using the function in an async context and awaiting the result
I've wrapped the code above in a class called API so the examples below are easier to follow,
class API {
static const URL = 'https://xxxhost/employee_actions3.php';
static Future<List<Employee>> getEmployees() async {
try {
final response = await http.post(Uri.parse(URL));
print("getEmployees >> Response:: ${response.body}");
if (response.statusCode == 200) {
List<Employee> list = parsePhotos(response.body);
return list;
} else {
throw("${response.statusCode} Failed to parse photos");
}
} catch (e) {
throw e;
}
}
}
Method 1: Providing a callback to .then, this method will allow you to work with async actions in a synchronous context, but be aware it will not halt the execution flow.
void main() {
API.getEmployees().then((resp) => print(resp)).catchError(e) => print(e);
}
Method 2: Async/Await, this method will allow you to access the data inline, that is var x = await myAsyncFunc() remember the await keyword requires the function to be called within an async context. And the await keyword will halt the execution flow till the future completes.
void main() async {
try {
final list = await API.getEmployees();
print(list);
} catch (e) {
print(e);
}
}
Using either one of the two methods outlined above will allow you to access the data of the list later on.
Additional Reading:
Async programming in dart
Futures and error handling

Related

Flutter Bloc/Cubit Error Handling - what is the best architectural approach?

I'm a beginner developer and I have problem with implementation of BloC framework. Let's assume that I have this code (Model, NetworkService, Repository, Cubit, State, Widget):
class NetworkService {
Future getData(Uri uri) async {
try {
http.Response httpsResponse = await http.get(
uri,
headers: {
// some headers //
},
);
if (httpsResponse.statusCode == 200) {
return httpsResponse.body;
} else {
throw 'Request failed with status: ${httpsResponse.statusCode}';
}
} catch (e) {
// What I shloud return here?
return e.toString();
}
}
Future<List<dynamic>> fetchData() async {
final uri = Uri.parse('some url');
var data = await getData(uri);
return = jsonDecode(data) as List;
}
}
class Repository {
final NetworkService networkService = NetworkService();
Future<List<SomeObject>> fetchDataList() async {
final dataRaw =
await networkService.fetchDataList();
return dataRaw.map((e) => SomeObject.fromJson(e)).toList();
}
}
class SomeCubit extends Cubit<CubitState> {
final Repository repository;
SomeCubit(this.repository) : super(LoadingState()) {
fetchDataList();
}
void fetchDataList() {
try {
repository
.fetchDataList()
.then((dataList) => emit(LoadedState(dataList)));
} catch (e) {
// What I shloud return here?
emit(ErrorState(e.toString()));
}
}
}
How to make this code "bullet proof" because I don't know how to "pass" error from NetworkService to Cubit? It works fine till I have dynamic responses in functions but in Repository class I want to return List of specific objects and when function fail I will return null. If I write try/catch I have to provide return statement in catch block - and I can't return List. I want to return some kind of Error...
I suggest that you use the excellent class named Either from the dartz package. It will allow you to return X if things went bad, and return Y if all is well, as such: Future<Either<X, Y>>
Then you can check on your variable (e.g. result) as follows: result.isLeft() for error, or do result.fold( ... ) to easily handle the return type (error or success).
In your particular case you could do as follows when returning from the repository to the cubit:
Future<Either<RepositoryError, List<SomeObject>>> fetchDataList() async { ... }
Where RepositoryError could be a class containing information about the type of error.
So in the cubit you do:
final result = await repository.fetchDataList();
emit(
result.fold(
(error) => ErrorState(error),
(dataList) => LoadedState(dataList)
)
);
Then you continue with this pattern all the way to NetworkService getData(). Either with the same common "error class" in the Repository and the NetworkService, or separate ones in the different layers and you "translate" between different "error classes". Perhaps it makes sense to have a NetworkServiceError that is returned there..
In your NetworkService you could do as follows:
Future<Either<NetworkServiceError, String>> getData(Uri uri) async { ... }
Future<Either<NetworkServiceError, List<dynamic>>> fetchData() async { ... }
This will give you great flexibility and passing of information from the service, to the repository and to the cubit.
You can let exceptions propagate through Futures from NetworkService up to the cubit, by removing the try/catch from getData.

Flutter mockito post always return null

I'm trying to test my code that makes a post to login from an API using a mocked http client, but instead of returning what I asked for, it returns null, I did the same test but changing the endpoint and method to GET and it worked perfectly. I'm currently using flutter's http to make the requests, but I've already tested it with Dio and the result was the same, below is my code
Future<String> signIn(String email, String password) async {
final Map<String, dynamic> body = {"email": email, "password": password};
final String url = url_base + Urls.auth_login;
final Map<String, String> customHeader = {
"Content-type": "application/json",
};
String returnCode;
try {
var x = jsonEncode(body);
http.Response response = await client.post(Uri.parse(url), body: x, headers: customHeader);
var parsedJson = json.decode(response.data);
if (parsedJson.containsKey("token")) {
returnCode = parsedJson["token"];
} else {
returnCode = parsedJson["non_field_errors"][0];
}
}catch (e) {
throw ServerException();
}
if (returnCode == null) {
throw ServerException();
} else {
return returnCode;
}
}
and the test case:
class ClientMock extends Mock implements http.Client {}
void main() {
RemoteData remoteData;
group('Test signIn', () {
test('Login with email and wrong password', () async {
final clientMock = ClientMock();
remoteData = RemoteData(client: clientMock);
String jsonMockResponse =
'{non_field_errors: [Unable to log in with provided credentials.]}';
when(clientMock.post(any))
.thenAnswer((_) async => http.Response(jsonMockResponse, 400));
String loginReturn =
await remoteData.signIn('test#email.com', 'password123');
expect(loginReturn,throwsA(const TypeMatcher<ServerException>()));
});
}
I've already tested some things like changing 'any' for exactly the same thing the real function gets and it didn't work either.
The actual test return 'Instance of 'ServerException'', an in debug mode i could see that the return is null, and the last if is the one who throws this exception.

Generic deserialization in Flutter / Dart

I'm trying to write a HTTP driver class that takes in a generic class and deserializes the response. I haven't found a good, clean way to do this in Flutter.
I've defined datamodel classes like this:
class MyClass {
String field1;
String field2;
MyClass.fromJson(Map<dynamic, dynamic> json)
: field1 = json["field1"],
field2 = json["field2"];
}
This works well and good if I do it manually...
MyClass makeRequest() {
Response response = http.get(url);
MyClass class = MyClass.fromJson(jsonDecode(response.body));
return class;
}
What I want, is to make a generic HTTP driver like this:
void makeRequest<T>() {
Response response = http.get(url);
T parsed = T.fromJson(jsonDecode(response.body));
return parsed;
}
Is there a way to do this in Flutter/Dart? I've been trying to figure out the right syntax to use a base class and extends but haven't gotten it. Any ideas?
This is what I usually use in my network call, feel free to use. Btw, I recommend the dio package for convenient headers and params config, as well as other error handling features.
// Define an extension
extension BaseModel on Type {
fromJson(Map<String, dynamic> data) {}
}
// For single object
Future<T> makeGetRequest<T>({String url, Map<String, dynamic> params}) {
return http
.get(buildUrl(url, params)) // Don't need the buildUrl() if you use Dio
.then((response) => handleJsonResponse(response))
.then((data) => T.fromJson(data));
// For list of object
Future<List<T>> makeGetRequestForList<T>({String url, Map<String, dynamic> params}) {
return http
.get(buildUrl(url, params)) // Don't need the buildUrl() if you use Dio
.then((response) => handleJsonResponse(response))
.then((data) => List<T>.from(data.map((item) => T.fromJson(item)));
}
// Helper classes without Dio
String buildUrl(String url, [Map parameters]) {
final stringBuilder = StringBuffer(url);
if (parameters?.isNotEmpty == true) {
stringBuilder.write('?');
parameters.forEach((key, value) => stringBuilder.write('$key=$value&'));
}
final result = stringBuilder.toString();
print(result);
return result;
}
// With Dio, you can simply do this:
final res = await API().dio
.get(url, queryParameters: params) // Don't need the [buildUrl] here
.then((response) => handleJsonResponse(response))
.then((data) => T.fromJson(data));
// Handle JSON response
handleJsonResponse(http.Response response, [String endpoint = '']) {
print(
'API: $endpoint \nCODE: ${response.statusCode} \nBODY: ${response.body}');
if (_okStatus.contains(response.statusCode)) {
return jsonDecode(response.body);
}
if (response.statusCode == HttpStatus.unauthorized) {
throw Exception(response.statusCode);
} else {
throw Exception("HTTP: ${response.statusCode} ${response.body}");
}
}
Usage:
// Example class
class Post {
final String title;
Post({this.title});
#override
Post.fromJson(Map<String, dynamic> data) : title = data['title'];
}
// Use the function
Future<Post> getPost() async {
final result = await makeGetRequest<Post>(params: {'post_id': 1});
return result;
}

How do I call a method as a parameter from a subclass in the Object class in dart?

I have a method inside a service class:
#override
Future<String> registerNewVoter(Object deviceAppInfo) async {
Dio dio = new Dio();
final url = API().endpointVoterUri(EndpointVoter.newVoter).toString();
final header = {'Content-type': 'application/json'};
final data = await deviceAppInfo; ///need to call the method getInfo() on the Object class which returns a future
final response =
await dio.post(url, data: data, options: Options(headers: header));
if (response.statusCode == 200) {
Map map = response.data;
final uuid = map['result']['voter_uuid'];
return uuid;
}
print(
'Request $url failed\nResponse: ${response.statusCode} ${response.statusMessage}');
throw response;
}
I'm using type Object deviceAppInfo as a parameter in the method to keep the service as pure as possible(adhering to mvvm principles). The subclass is DeviceAppInfo which has an async method called getInfo()(and where the data comes from) which is supposed to be assigned to data(see the comments in the code). I'm struggling to see how I can keep the class decoupled from DeviceAppInfo class. Any suggestions...? I'm thinking of calling a factory constructor but not sure how to implement it. Here is my DeviceAppInfo class:
class DeviceAppInfo {
DeviceAppInfo({
this.platform,
this.platformVersion,
this.appVersion,
});
final String platform;
final String platformVersion;
final String appVersion;
Map<String, dynamic> toMap() => {
'platform': this.platform,
'platform_version': this.platformVersion,
'app_version': this.appVersion,
};
Future<Map<String, dynamic>> getInfo() async {
final values = await Future.wait([
getPlatform(),
getPlatformVersion(),
getProjectVersion(),
]);
return DeviceAppInfo(
platform: values[0],
platformVersion: values[1],
appVersion: values[2],
).toMap();
}
Future<String> getPlatform() async {
try {
if (Platform.isIOS) {
return 'ios';
}
return 'android';
} on PlatformException catch (e) {
return e.toString();
}
}
Future<String> getPlatformVersion() async {
try {
final platformVersion = await GetVersion.platformVersion;
return platformVersion;
} on PlatformException catch (e) {
return e.toString();
}
}
Future<String> getProjectVersion() async {
try {
final projectVersion = await GetVersion.projectVersion;
return projectVersion;
} on PlatformException catch (e) {
return e.toString();
}
}
}
I believe that DeviceAppInfo is a clear collaborator of your service, and hiding it behind Object is simply bad engineering:
it will make your Api hard to use correctly and easy to use incorrectly
Your api is no longer self-documenting, without reading the docs or code it is impossible to use it correctly.
However, it can be discussed if it should be exposed as a parameter or provided to the constructor of your service.
Having said that, There are at least 3 options that will decouple your service from DeviceAppInfo:
Option 1: Pass in the result of getInfo() to your method
least questionable and a common form of decoupling inbound data
I am a bit sceptical if you use a Map as an input type, it is still easy to provide a map with incorrect keys
Option 2: take a function as an argument
Function a bit harder to use, it is not evident what functions accross the codebase can be used (compared to a class)
Option 3: cast to dynamic
Please dont do that
Most closely matches your goal from question
function is extremely hard to use correctly Without reading docs / code
You change compile-time errors to runtime errors
Is this what you want?
#override
Future<String> registerNewVoter(DeviceAppInfo deviceAppInfo) async {
Dio dio = new Dio();
final url = API().endpointVoterUri(EndpointVoter.newVoter).toString();
final header = {'Content-type': 'application/json'};
final data = await deviceAppInfo.getInfo();
final response =
await dio.post(url, data: data, options: Options(headers: header));
if (response.statusCode == 200) {
Map map = response.data;
final uuid = map['result']['voter_uuid'];
return uuid;
}
print(
'Request $url failed\nResponse: ${response.statusCode} ${response.statusMessage}');
throw response;
}
NOTE: I just changed the type of deviceAppInfo from Object to DeviceAppInfo

Singleton class for http requests

Can you give some advice how to design class for api requests in flutter? I'm ios developer and I used singleton classes with alamofire. If you provide some code it would be great!
class Client: ApiBase {
static let shared = Client()
private override init() {}
func login(phoneNumber: String, password: String, completion: #escaping(_ error: String?) -> Void) {
let params: [String : String] = [
"userId" : phoneNumber,
"password" : password,
]
baseRequest(route: ApiRouter.login(), params: params) { (response) in
if let json = response.json {
Session.current.sessionId = json["sessionId"].string
}
completion(response.error)
}
}
}
How login method called:
#IBAction func singin(_ sender: TransitionButton) {
Client.shared.login(phoneNumber: "12312", password: "123") { (error) in
guard error == nil else {
// show error
return
}
// navigate to home page
}
}
In flutter you don't have to deal with the relative nastiness of IBActions, protocols as callback, or retain cycles, and you have async and await to help out.
There's a few ways you could do the API calls - one would be to simply put them right in the same code as your UI. That has downsides, but it is certainly readable.
class WhateverMyComponentIsState extends State<WateverMyComponentIs> {
Future<String> _doLogin({#required String phoneNumber, #required String password}) async {
final response = await http.post(LOGIN_URL, body: {'userId': phoneNumber, 'password': password})
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(body);
return jsonResponse['sessionId'];
} else {
... error handling
}
}
String phoneNumber;
String password;
#override
Widget build(BuildContext context) {
return ...(
child: FlatButton(
onPressed: () async {
final sessionId = await _doLogin(phoneNumber: phoneNumber, password: password);
... do whatever - setState(() => loggedIn = true), or Navigator.push ...
}
),
)
}
}
If you wanted, you could extract all of the api calls into a different class - they could be static methods, but that makes it so that it's harder to write good tests if you ever decide to do that.
My personal recommendation is to use a form of more or less 'dependency injection', by utilizing InheritedWidget to provide an implementation of a class that actually performs the login (and could hold the sessionId). Rather than implementing all of that yourself, though, you could use the ScopedModel plugin which I personally like very much as it greatly reduces the amount of boilerplate needed.
If you use ScopedModel properly (which I'll leave as an exercise for you - I'm pretty sure there's other questions about that), you can use it or a class it provides to do the http request, and then have the sessionId stored in the ScopedModel.
The beauty of that is that if you were to ever get to writing tests (or have to deal with two slightly servers, etc), you could then replace the ScopedModel with a different ScopedModel which implemented the same interface but doesn't actually perform http requests or performs them differently.
In flutter you should create a class something like this
class User {
String name;
String pass;
User({
this.name,
this.pass,
});
User.fromJson(Map<String, dynamic> json) {
name = json['name'];
pass= json['pass'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['pass'] = this.pass;
return data;
}
}
Now create the list of type User class something like this
final List<User> user;
Now call the URL (API) for user Auth
Future<void> validateUsr() async {
var client = new http.Client();
try {
var response = await client.get(
'https://xxxxxxxx/wp-json/jwt-auth/v1/token?username=xxxxx2&password=xxxxxx');
if (response.statusCode == 200) {
var data = json.decode(response.body);
var list = data as List;
setState(() {
user=list.map<User>((i) => User.fromJson(i)).toList();
});
} else {
print('Somthing went wrong');
}
} catch (e) {
print(e);
} finally {
client.close();
}
}
Hope this helped you