Using injectable for third party abstract class in flutter - flutter

I have used package http in my project and thus, I have Client's instance (which comes from package http) as a dependency, which is an abstract class. So, how should I annotate with proper annotations?
In injectable's documentation, there is information on how to register third-party dependencies and how to register abstract classes. But how can I register third-party abstract class?
This is my code
class TokenValueRemoteDataSourceImpl implements TokenValueRemoteDataSource {
TokenValueRemoteDataSourceImpl(this.client);
final http.Client client;
#override
Future<TokenValueModel> getAuthToken({
required EmailAddress emailAddress,
required Password password,
}) async {
final emailAddressString = emailAddress.getOrCrash();
final passwordString = password.getOrCrash();
const stringUrl = 'http://127.0.0.1:8000/api/user/token/';
final response = await client.post(
Uri.parse(stringUrl),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(
{
'email': emailAddressString,
'password': passwordString,
},
),
);
if (response.statusCode == 200) {
return TokenValueModel.fromJson(
json.decode(response.body) as Map<String, dynamic>,
);
} else {
throw ServerException();
}
}
}
How should I write my register module for a third-party abstract class?
I did see this on injectable's documentation
#module
abstract class RegisterModule {
#singleton
ThirdPartyType get thirdPartyType;
#prod
#Injectable(as: ThirdPartyAbstract)
ThirdPartyImpl get thirdPartyType;
}
But I didn't understand what I should replace ThirdPartyImpl with in my code.

You don't necessarily need to define an abstract class to inject your dependencies. So for in your case, to register a third-party class, you can use the same type without having an abstract and concrete class separately. See the below example of how to register the http Client class that is imported from the http package:
#module
abstract class YourModuleName {
#lazySingleton // or #singleton
http.Client get httpClient => http.Client();
}
Then you can use the http Client anywhere using the global GetIt variable you have, like this:
yourGetItVariableName.get<http.Client>(); or GetIt.I.get<http.Client>();

Related

Flutter Injectable Inject a Third Party Dependency

I've been spinning my wheels for hours on the simple question of how to inject http.Client into a flutter class when using injectable. They reference doing this in a module (as suggested in this post), but I can't figure that out either.
This is my file (abstract and concrete classes):
import 'dart:convert';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http;
import 'package:injectable/injectable.dart';
import 'package:myapp_flutter/core/errors/exceptions.dart';
import 'package:myapp_flutter/data/models/sample_model.dart';
abstract class ISampleRemoteDataSource {
/// Throws a [ServerException] for all error codes.
Future<SampleModel> getSampleModel(String activityType);
}
#Injectable(as: ISampleRemoteDataSource)
class SampleRemoteDataSourceImpl extends ISampleRemoteDataSource {
final http.Client client;
final baseUrl = "https://www.boredapi.com/api/activity?type=";
final headers = {'Content-Type': 'application/json'};
SampleRemoteDataSourceImpl({#factoryParam required this.client});
#override
Future<SampleModel> getSampleModel(String activityType) async {
Uri uri = Uri.parse(baseUrl + activityType);
GetIt.I.get<http.Client>();
final response = await client.get(uri, headers: headers);
if (response.statusCode == 200) {
return SampleModel.fromJson(json.decode(response.body));
} else {
throw ServerException();
}
}
}
I thought declaring it as a factory param in the constructor would do it, but I was wrong. Declaring the abstract class as a module doesn't do it (and seems very wrong, also). I just don't know.
This should work:
First implement a register module file
#module
abstract class RegisterModule {
//add http client
#lazySingleton
http.Client get httpClient => http.Client();
}
class $RegisterModule extends RegisterModule {}
Then, after generating injection.config.dart file your http Client should be mentioned in initGetIt class
You will have to register them as module as described below
https://pub.dev/packages/injectable#Registering-third-party-types

How to use http interceptor in a flutter project?

I have to add header to all my Api's. I was told to use http interceptor for that. But i am not able to understand how to do it as i am new to flutter. Can anyone help me with example?
you can use http_interceptor.
it works as follows,
first you create your interceptor by implementing InterceptorContract
class MyInterceptor implements InterceptorContract {
#override
Future<RequestData> interceptRequest({RequestData data}) async {
try {
data.headers["Content-Type"] = "application/json";
} catch (e) {
print(e);
}
return data;
}
#override
Future<ResponseData> interceptResponse({ResponseData data}) async => data;
}
then create a client and inject this interceptor in it
Client _client = InterceptedClient.build(interceptors: [
MyInterceptor(),
]);
You can add multiple interceptors to the same client, say you want one to refresh the token, one to add/change headers, so it will be something like this:
Client _client = InterceptedClient.build(interceptors: [
RefreshTokenInterceptor(),
ContentTypeInterceptor(),
/// etc
]);
note that every interceptor must implement the InterceptorContract
Now anytime you use this client, the request will be intercepted and headers will be added to it. You can make this client a singleton to use the same instance across the app like this
class HttpClient {
Client _client;
static void _initClient() {
if (_client == null) {
_client = InterceptedClient.build(
interceptors: [MyInterceptor()],
);
}
}
/// implement http request with this client
}

How to set base url to flutter http package?

I tried using http package of flutter and create a custom client with headers.
Code
class ApiClient extends http.BaseClient {
final http.Client _inner;
ApiClient(this._inner);
_setHeaders() => {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer token here...'
};
Future<http.StreamedResponse> send(http.BaseRequest request) {
request.headers.addAll(_setHeaders());
return _inner.send(request);
}
}
How can I add a base URL to my custom client?
Since ApiClient inherits http.BaseClient, you should be able to have access to other methods as well. Simply access the method on your ApiClient for example.
var baseUrl = Uri.parse('https://example.com/');
var response = await ApiClient.post(baseUrl);
I use a similar approach on my projects:
class ApiClient extends http.BaseClient {
final http.Client _inner;
final String baseUrl;
ApiClient(this._inner, this.baseUrl);
Uri url(String path, [Map<String, String?>? queryParameters]) {
return Uri.parse('$baseUrl$path').replace(queryParameters: queryParameters);
}
// other methods ...
}
Usage sample:
final api = ApiClient(inner, 'https://testhost/api/v1');
final response = await api.post(api.url('/test', {'q': 'a'}));

Flutter Global Http Interceptor

I would like to know if it is possible to have a global HTTP interceptor to attach token in header for all requests in Flutter? I've searched a lot and couldn't find any information as where and how to set it up as globally. Thanks a lot!
You can extend BaseClient and override send(BaseRequest request):
class CustomClient extends BaseClient {
static Map<String, String> _getHeaders() {
return {
'Authentication': 'c7fabcDefG04075ec6ce0',
};
}
#override
Future<StreamedResponse> send(BaseRequest request) async {
request.headers.addAll(_getHeaders());
return request.send();
}
}
In the above example the 'Authentication': 'c7fabcDefG04075ec6ce0' is hardcoded and not encrypted which you should never do.
Using dio package u can do that :
Dio dio = Dio(BaseOptions(
connectTimeout: 30000,
baseUrl: 'your api',
responseType: ResponseType.json,
contentType: ContentType.json.toString(),
))
..interceptors.addAll(
[
InterceptorsWrapper(onRequest: (RequestOptions requestOptions) {
dio.interceptors.requestLock.lock();
String token = ShareP.sharedPreferences.getString('token');
if (token != null) {
dio.options.headers[HttpHeaders.authorizationHeader] =
'Bearer ' + token;
}
dio.interceptors.requestLock.unlock();
return requestOptions;
}),
// other interceptor
],
);
Flutter provides http_interceptor.dart package.
Sample
class LoggingInterceptor implements InterceptorContract {
#override
Future<RequestData> interceptRequest({RequestData data}) async {
print(data);
return data;
}
#override
Future<ResponseData> interceptResponse({ResponseData data}) async {
print(data);
return data;
}
}
This answer is an extension of Felipe Medeiros's answer that I could not edit. It is not actually a global way to attach a token to every requests, but should be considered nonetheless to create interceptors/middleware.
BaseClient is part of the native http package. You can extend BaseClient and override send(BaseRequest request):
class BearerTokenMiddleware extends BaseClient {
final Future<String> Function() getBearerToken;
BearerTokenMiddleware({required this.getBearerToken});
#override
Future<StreamedResponse> send(BaseRequest request) async {
request.headers.addAll({
'Authorization': 'Bearer ${await getBearerToken()}',
});
return request.send();
}
}
When one of your classes needs the http client, inject the BaseClient abstraction to the constructor. Exemple:
class HTTPTodoGateway implements TodoGateway {
final BaseClient httpClient;
HTTPTodoGateway ({required this.httpClient});
getTodoById(string todoId) {
httpClient.get(Uri.parse('https://mytodos/$todoId'));
}
}
You can then create a new instance of HTTPTodoGateway with an instance of BearerTokenMiddleware that will wrap your requests with an authentication bearer header.

Different returns in different responses & to return a part of the response in Flutter

I have a login API that returns me following JSON If the user's credentials match.
{
"user": {
"ok": true,
"data": {...}
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
}
If the user's credentials do not match. I get.
{
"user": {
"ok": false
},
"message": "Oops! Wrong password."
}
I have this Copper API code
import ...
part 'authApiServices.chopper.dart';
#ChopperApi(baseUrl: "/auth")
abstract class AuthApiServices extends ChopperService {
#Post(path: "/login", headers: {'Content-Type': 'application/json'})
Future<Response<User>> login(
#Body() Map<String, String> body,
);
static AuthApiServices create() {
final client = ChopperClient(
baseUrl: 'https://URL/api',
services: [
_$AuthApiServices(),
],
converter: AuthConverter(),
);
return _$AuthApiServices(client);
}
}
Now depending upon the user.ok I want my app to respond.
Note: The API will always send response status 200.
If the login is successful I only want to store/return the user.data not the whole response also store the token in shared preferences.
So I have created a User model class using app.quicktype.io
Now, I know that I have to play with this converter: AuthConverter() and add the whole logic here.
So this is my AuthConverter class.
import 'package:chopper/chopper.dart';
import 'dart:convert';
import 'package:venue_monk/models/user_model.dart';
class AuthConverter extends JsonConverter {
#override
Response<BodyType> convertResponse<BodyType, InnerType>(Response response) {
return decodeJson(response) as Response<BodyType>;
}
#override
Response<User> decodeJson(Response response) {
final responseJson = json.decode(response.body);
if(responseJson['user']['ok']) {
return response.replace(
body: responseJson['user']['data'],
);
}
}
}
Doing this I get an error
Cannot convert from Response<Dyanmic> to Response<User>
I also need help with returning diff things w.r.t the user.ok value.
I know I'm asking a lot. But if anyone could help as I'm very much new to Flutter.
Thank You!.