Book: Flutter Apprentice - Try to make a generic ModelConverter from a Api Response - flutter

In the excellent Flutter book by Raywenderlich, Chapter 13 is dedicated to obtaining Responses from an api using libraries Chopper and JsonConverter.
The code in Github is here
He also proposes the use of a response wrapper like functional programming, of the type Success/Error.
The ModelConverter from Response to Success/Error wrapper, applies to the APIRecipeQuery model and only uses one method in one line final recipeQuery = APIRecipeQuery.fromJson(mapData);. It seems quite logical to make a generic convert, since it is a very useful class.
import 'dart:convert';
import 'package:chopper/chopper.dart';
import 'model_response.dart';
import 'recipe_model.dart';
class ModelConverter implements Converter {
#override
Request convertRequest(Request request) {
// Add a header to the request that says you have a request type of application/json using jsonHeaders.
// These constants are part of Chopper.
final req = applyHeader(
request,
contentTypeKey,
jsonHeaders,
override: false,
);
return encodeJson(req);
}
#override
Response<BodyType> convertResponse<BodyType, InnerType>(Response response) {
return decodeJson<BodyType, InnerType>(response);
}
Request encodeJson(Request request) {
final contentType = request.headers[contentTypeKey];
// Confirm contentType is of type application/json.
if (contentType != null && contentType.contains(jsonHeaders)) {
return request.copyWith(body: json.encode(request.body));
}
return request;
}
Response decodeJson<BodyType, InnerType>(Response response) {
final contentType = response.headers[contentTypeKey];
var body = response.body;
if (contentType != null && contentType.contains(jsonHeaders)) {
body = utf8.decode(response.bodyBytes);
}
try {
final mapData = json.decode(body);
if (mapData['status'] != null) {
return response.copyWith<BodyType>(
body: Error(Exception(mapData['status'])) as BodyType);
}
/*
The only line is next
*/
final recipeQuery = APIRecipeQuery.fromJson(mapData);
return response.copyWith<BodyType>(
body: Success(recipeQuery) as BodyType);
} catch (e) {
chopperLogger.warning(e);
return response.copyWith<BodyType>(body: Error(e) as BodyType);
}
}
So, I have tried by passing the model in the constructor as a parameter:
class ModelConverter <T extends JsonConverter> implements Converter {
final T model;
ModelConverter ({#required this.model});
and
I invoke it in recipe_service.dart with converter: ModelConverter(model: APIRecipeQuery), but I don't know how to reference the model statically, and can't access the method model.fromJson
Next, I have tried passing just the function converter:
class ModelConverter implements Converter {
Function fromJson;
ModelConverter ({# required this.fromJson});
with a getter in the API, and in recipe_service.dart with converter: ModelConverter(fromJson: APIRecipeQuery.fjConverter)
class APIRecipeQuery {
static Function get fjConverter => _ $ APIRecipeQueryFromJson;
But I can't get it to work.
What would be the best approach to make the ModelConverter generic?
Thnks in advance.

Solved in this post
model_converter.dart
. . .
typedef CreateModelFromJson = dynamic Function(Map<String, dynamic> json);
class ModelConverter<Model> implements Converter {
final CreateModelFromJson fromJson;
ModelConverter({#required this.fromJson});
. . .
final query = fromJson(mapData) as Model;
. . .
and recipe_service.dart
. . .
converter: ModelConverter<APIRecipeQuery>(
fromJson: (json) => APIRecipeQuery.fromJson(json),
),
. . .

Related

Flutter: How to implement non-standard code flow using `injectable` package?

Let's say I need this:
class EndpointProvider {
String getEndpoint(String trigger) {
// skipped for clarity
}
}
class MyHttpClient implements BaseHttpClient {
MyHttpClient(this.baseUrl, [String accessToken = '']);
}
class MyRemoteDataProvider extends BaseDataProvider {
MyRemoteDataProvider(this.httpClient)
final BaseHttpClient httpClient;
}
The current code flow is:
final endpointProvider = EndpointProvider();
final endpoint = endpointProvider.getEndpoint('trigger');
final accessToken = getTokenFromStorage() ?? '';
final httpClient = MyHttpClient(endpoint.url, accessToke );
final dataProvider = MyRemoteDataProvider(httpClient);
dataProvider.do();
Is it posssible to implement this using injectable?
You can try this:
#lazySingleton
class EndpointProvider {
String getEndpoint(String trigger) {
return 'https://api.example.com/$trigger';
}
}
#LazySingleton(as: BaseHttpClient)
class MyHttpClient implements BaseHttpClient {
final String baseUrl;
MyHttpClient(EndpointProvider endpointProvider, [#Named('accessToken') String accessToken]) : baseUrl = endpointProvider.getEndpoint('my_endpoint');
}
#lazySingleton
class MyRemoteDataProvider extends BaseDataProvider {
MyRemoteDataProvider(this.httpClient)
final BaseHttpClient httpClient;
}
and in the same file of injectable configuration you can put:
#module
abstract class Module {
#Named('accessToken')
String get accessToken => '';
}

how to add a token head to a request using HttpClient from IHttpClientFactory in Blazor

I am trying to use JWT in my API, and configuration is completed, can use postman tool to access data from it. However when I use Blazor as front end to access it , the request doesn't have token, so always give a 401 code.
Below is my Blazor code.
program.cs
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
builder.Services.AddScoped(
sp => sp.GetService<IHttpClientFactory>().CreateClient("OptionAPI"));
OptionService.cs
public class OptionService : IOptionService {
private readonly HttpClient _httpClient;
public OptionService(HttpClient httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
_httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await _httpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
I tired use " new AuthenticationHeaderValue("Bearer", "token");" to attach token in header, but its not working, still give 401 code.
And I also tried use
private readonly IHttpClientFactory _httpClient;
public OptionService(IHttpClientFactory httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
var newHttpClient = _httpClient.CreateClient();
newHttpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await newHttpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
it's also not working, give me an error,
Unhandled exception rendering component: A suitable constructor for type 'Services.OptionService' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.
System.InvalidOperationException: A suitable constructor for type .....
Can anyone has a simple way to attach token in request header?
Thanks in advance.
I think the good option is :
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
Could you check if the token is present in header or not?
Your error is most likely related to how the OptionService is being registered in dependency injection. It either needs an empty constructor adding - and/or - you need to ensure that the constructor has all of its dependencies registered correctly in the ServicesCollection too.
The exception is quite explicit:
Ensure the type is concrete and all parameters of a public constructor
are either registered as services or passed as arguments. Also ensure
no extraneous arguments are provided
I gave a similar answer here. Basically you need to include the BaseAddressAuthorizationMessageHandler when defining your httpclients. If you're using a typed httpclient, you can inject the IAccessTokenProvider and get the token from there. Kinda like this:
public class MyHttpClient(IAccessTokenProvider tokenProvider, HttpClient httpClient)
{
_tokenProvider = tokenProvider;
_httpClient = httpClient;
}
private async Task RequestAuthToken()
{
var requestToken = await _tokenProvider.RequestAccessToken();
requestToken.TryGetToken(out var token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
}
public async Task<IEnumerable<ReplyDto>> SendHttpRequest()
{
await RequestAuthToken();
return await JsonSerializer.DeserializeAsync<IEnumerable<ReplyDto>>(
await _httpClient.GetStreamAsync("api/getendpoint"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}

How to send with 'params.put' in Dart or Flutter JsonObjectLite

In java it would be something like this ...
public Request compose(LoginDevice login) {
JSONObject params = new JSONObject();
try {
if (login.granType != null)
params.put("grant_type", login.granType);
if (login.clientId != null)
params.put("client_id", login.clientId);
} catch (JSONException e) {
e.printStackTrace();
}
return (Request)new BaseRequest("oauth/v2/token", params.toString(), new HashSet());
}
And in Dart and tried something similar but it doesn't work... the parameter 'put' does not exist in JsonObjectLite...
Request compose(LoginDevice login)
{
JsonObjectLite params = new JsonObjectLite();
try {
if (login.granType != null) {
params.put("grant_type", login.granType);
}
if (login.clientId != null) {
params.put("client_id", login.clientId);
}
} on JsonObjectLiteException catch (e) {
print(e);
}
return new BaseRequest("oauth/v2/token", params.toString(), new HashSet());
}
How could I do it? Thank you
The class JsonObjectLite doesn't contain the method put.
How you can understand dart doesn't is Java, in this cases your the class JsonObjectLite has a method called putIfAbsent, the implementation is the following
/// If [isImmutable] is false, or the key already exists,
/// then allow the edit.
/// Throw [JsonObjectLiteException] if we're not allowed to add a new
/// key
#override
void putIfAbsent(dynamic key, Function() ifAbsent) {
if (isImmutable == false || containsKey(key)) {
_objectData.putIfAbsent(key, ifAbsent);
} else {
throw const JsonObjectLiteException('JsonObject is not extendable');
}
}
look also the Source code
So an example of code should be the following
import 'package:json_object_lite/json_object_lite.dart';
class AuthorAnswer {
var _username;
var _status;
AuthorAnswer(this._username, this._status);
String get username => _username;
String get status => _status;
}
int main() {
var author = '#vincenzopalazzo';
var sentences = 'Follow me on Github';
var authorObject = AuthorAnswer(author, sentences);
try{
JsonObjectLite params = new JsonObjectLite();
params.isImmutable = false;
params.putIfAbsent("author", () => authorObject.username);
params.putIfAbsent("sencence", () => authorObject.status);
print(params.toString());
} on JsonObjectLiteException catch (err){
print('--------- ERROR ----------');
print(err);
}
return 0;
}
You should be set the params.isImmutable = false and after you can add your propriety, with your logic.
In my opinion, I don't see any motivation to use this library, dart have 2 types of the module to implement the serialization, and I think should better use it because on the web exist the documentation, like this dart json, flutter json
Inside the flutter app, there are also the line guides, for the small application you can use dart:convert:json also for the other you can use the json_serializable
I want to add also an example of dart:convert
/**
*
*/
import "dart:core";
import "dart:convert";
class ClassToJsonOne {
var _propOne;
var _propTwo;
ClassToJsonOne(this._propOne, this._propTwo);
Map<String, dynamic> toJSon() => {
'prop_one': _propOne,
'prop_two': _propTwo
};
ClassToJsonOne.fromJson(Map<String, dynamic> json):
_propOne = json['prop_one'],
_propTwo = json['prop_two'];
#override
String toString() => 'First Class: $_propOne, $_propTwo';
}
class ClassToJsonTwo{
var _propOne;
var _propTwo;
ClassToJsonTwo(this._propOne, this._propTwo);
Map<String, dynamic> toJSon() => {
'prop_one': _propOne,
'prop_two': _propTwo
};
ClassToJsonTwo.fromJson(Map<String, dynamic> json):
_propOne = json['prop_one'],
_propTwo = json['prop_two'];
#override
String toString() => 'Second Class: $_propOne, $_propTwo';
}
main(List<String> args) {
print('------- Declare Objecr -------\n');
var objectToJsonOne = ClassToJsonOne('I am the fist object', 'empty');
var objectToJsonTwo = ClassToJsonTwo('I contains the first object', 'empty');
String jsonStringObjOne = jsonEncode(objectToJsonOne.toJSon());
print('\n---------- Object one JSON format ---------\n');
print(jsonStringObjOne);
String jsonStringObjTwo = jsonEncode(objectToJsonTwo.toJSon());
print('\n---------- Object one JSON format ---------\n');
print(jsonStringObjTwo);
print('\n---------- DECODE JSON to OBJECT ---------\n');
var fromJsonObjectOne = jsonDecode(jsonStringObjOne);
print(fromJsonObjectOne.toString());
var fromJsonObjectTwo = jsonDecode(jsonStringObjTwo);
print(fromJsonObjectTwo.toString());
}
Inside the classes, you can see the following methods
Map<String, dynamic> toJSon() => {
'prop_one': _propOne,
'prop_two': _propTwo
};
ClassToJsonTwo.fromJson(Map<String, dynamic> json):
_propOne = json['prop_one'],
_propTwo = json['prop_two'];
The result of the method toJSon, you should be pass to the method of the library jsonEncode and when you go to deserialize you can use the library method jsonDecode(youtStringJSOn) and the result you can pass to the method of your class fromJson.
In addition, you can configure the library json_serializable.
In conclusion, I want to fix my comment
I think the json_serializable worked how GSON, I can make an example for you, on this day.
On flutter, documentation has reported this text
Is there a GSON/Jackson/Moshi equivalent in Flutter?
The simple answer is no.
Such a library would require using runtime reflection, which is disabled in Flutter. Runtime reflection interferes with tree shaking, which Dart has supported for quite a long time. With tree shaking, you can “shake off” unused code from your release builds. This optimizes the app’s size significantly.
Since reflection makes all code implicitly used by default, it makes tree shaking difficult. The tools cannot know what parts are unused at runtime, so the redundant code is hard to strip away. App sizes cannot be easily optimized when using reflection.
Although you cannot use runtime reflection with Flutter, some libraries give you similarly easy-to-use APIs but are based on code generation instead. This approach is covered in more detail in the code generation libraries section.
you can found the source code inside this answer here

How to Access Provider with out Context in Flutter

I have a problem with the Flutter Provider pattern, I need to access Provides from Class where I don't have Context.
Providers :
import 'package:flutter/foundation.dart';
class TokenProvider with ChangeNotifier {
TokenService tokenService = TokenService();
String _accessToken = '';
String get accessToken {
return _accessToken;
}
dynamic setAccessToken(data) async {
_accessToken = data;
}
}
Class :
import '../constants/constants.dart';
import '../models/models.dart';
import './network-call/base-service.dart';
class TokenService extends BaseService {
Future<String> getToken() async {
final dynamic response = await serviceCall(
url: ApiName().apiName(api: ServiceName.TOKEN),
method: ApiMethod.POST,
queryParameters: {
'id': Preferences().env['id'],
'secret': Preferences().env['secret'],
'type': 'rrrr'
});
Need to set this responce Data in Providers
}
}
Thank you.
try to add "notifyListeners();"
dynamic setAccessToken(data) async {
_accessToken = data;
notifyListeners();
}
I use to pass the context when needed, as dlohani suggests in the comment at the question, but I found myself in the same situation and applied a solution inspired by the communication pattern used between isolates: messages exchange.
The Provider class need to have a ReceiverPort field which listens to request messages. Ones the message reaches this listener you're inside the Provider, so you can retrieve data and send them back, again in the ReceiverPort's fashion, that is using the sendPort of the ReceiverPort registered in the requesting class.
In code below I suppose messages are Maps to clarify the type of data exchanged:
class SomeProvider with ChangeNotifier {
var _innerData = yourData;
var _providerReceiverPort = ReceiverPort();
SomeProvider() {
// The registration is necessary to "publish" the receiver port
IsolateNameServer.registerPortWithName(
_providerReceiverPort, "SomeProviderPort");
_providerReceiverPort.listen(
(message) {
// I retrieve the port to send the response to
var port = message["sendPort"];
// The answer follows the rules of messaging: maps and lists are ok
port.send({"data": _innerData.getSomething()});
}
);
}
}
class SomeClient {
var _clientReceiverPort = ReceiverPort();
someFunction(){
// First step: prepare the receiver to obtain data
_clientReceiverPort.listen(
(message) {
// Data are stored in the map containing a "data" key
var response = message["data"];
...
}
);
// Now I can retrieve the provider port by using the same name it uses to publish it
var providerPort = IsolateNameServer.lookupPortByName("SomeProviderPort");
// The message must include the sendPort to permit the provider to address the response
providerPort.send({"sendPort": _clientReceiverPort.sendPort});
}
}
The drawback of this solution is that the Provider doesn't work as a provider for the SomeClient class. You can obviously notify if any change in the listener is important for the subscribers: for example, I use this pattern to update data in the provider from a background isolate.
As I said, this is a workaround, any suggestion to improve is welcome.

Passing constructor as argument in Flutter

I have API communication service in my Flutter app with 10+ different services, and 100+ API calls that heed to parse data. In order to reuse code I've decided to create some common parsing code that is going to parse data from API:
ApiResponse handleObjectResponse({
#required http.Response serverResponse,
#required Function objectConstructor,
}) {
if (serverResponse.statusCode == 200) {
dynamic responseObject = objectConstructor(json.decode(serverResponse.body));
return ApiResponse(responseObject: responseObject);
} else {
ApiError error = responseHasError(serverResponse.body);
return ApiResponse(error: error);
}
}
This way I am able to parse JSON object from API in a reusable way no matter what the Object class is, just by passing constructor function to this method.
When I call this method in any of the Services I've created for fetching data like this:
handleObjectResponse(serverResponse: response, objectConstructor: ChartData.fromJson);
I get error: The getter 'fromJson' isn't defined for the class 'ChartData'.
Try importing the library that defines 'fromJson', correcting the name to the name of an existing getter, or defining a getter or field named 'fromJson'.
Where I think the problem is is in this model class and factory statement, but I don't know how to fix it:
class ChartData {
List<ChartDataPoint> points;
ChartData({
this.points,
});
factory ChartData.fromJson(Map<String, dynamic> json) {
List jsonPoints = json["data"];
return ChartData(
points: List.generate(jsonPoints.length,
(i) => ChartDataPoint.fromJsonArray(jsonPoints[i])));
}
}
You cannot pass constructors as functions. You need to create a function what will call the constructor instead:
(int a) => Foo(a);
Just a 2022 update: since 2.15 it's possible by Class.new, see the complete issue: https://github.com/dart-lang/language/issues/216.
class A {
final String a;
const A(this.a);
#override
String toString() => 'A($a)';
}
class B {
final String b;
const B(this.b);
#override
String toString() => 'B($b)';
}
void main() {
final List<Object Function(String)> constructors = [A.new, B.new];
for (final Object Function(String) constructor in constructors) {
final Object instance = constructor('My Constructor Parameter');
if (instance is A) {
print(instance.toString());
}
}
}
Note that if you're using named params, both class constructors must have the same param name, otherwise the constructor signatures won't match and then it will generate this static error:
The element type X can't be assigned to the list type Y.