retrofit.dart
retrofit.dart copied to clipboard
Support Flavors (different environments)
I need to support two different environments (different baseUrl). Retrofit code generation is based on constant baseUrl.
It would be great to have the ability to generate code that will use a dynamic baseUrl, which will be provided on run time based on the flavor.
You can pass URL by parameter, here is how I do it:
@RestApi()
@Injectable(as: UnifiApi)
abstract class RestUnifiApi implements UnifiApi {
@factoryMethod
factory RestUnifiApi(Dio dio, AppConfig appConfig) =>
_RestUnifiApi(dio, baseUrl: appConfig.apiUrl);
...
}
Hey @leoshusar thanks for replying. Is there any better solution for Bloc?
Also, I would appreciate it if you could explain a little more about your solution.
No problem!
I am using dependency injection for everything (things not related to Flutter Widget
s, I use Provider
for blocs), for DI I use injectable
package.
First I load and register my config:
@singleton
@preResolve
Future<RawConfig> config() async {
final envMap = await EnvLoader.load(directory: 'assets/env');
return RawConfig.from(envMap);
}
this loads either .env
or .env.dev
asset depending on if I'm in release or debug mode; it also supports platform environment and --dart-define
for non-web applications.
Then I have concrete AppConfig
class that has injected RawConfig
and exposes strongly typed properties instead of string map:
@singleton
class AppConfig extends _$AppConfig {
AppConfig(super.config);
String get apiUrl => _config['API_URL']!;
}
Finally I can inject AppConfig
everywhere I want, including my retrofit services. Then I inject them to blocs.
Also injectable
has support for different environments, you can register main service for release and mock service for e.g. tests.
Hey @leoshusar! Thanks for the support. I just came back from a vacation.
I still don't understand how you use this method to generate requests using Retrofit. The Retrofit generated file should adapt to different environments (the baseUrl should change base the flavor), and I think that the above example does not solve this issue.
Can you please provide me a link to source code which uses your method? Because I just can't find one (also in your public repos π). I want to see the full picture of using injectable with Retrofit.
I would appreciate if you could post an example that uses Retrofit and injectable using different environments π
Hi @saveKenny, hope you had a great time!
I just made very simple project which shows how I use Retrofit + Injectable, this should be enough to understand my main concept.
There is a lot of annotation and generator magic, it's kinda similar to Java world (only those annotations, Java uses reflection for all of that). But I'm not a Java dev eitherπ If you need anything to clear up, I can try to explain.
@leoshusar Thanks a lot!! Great example! π
@leoshusar In case I'm using Bloc, I think I don't need to use GetIt for DI, since I can just use the RepositoryProvider for that.
Yeah, that's an option too, but you need BuildContext
for accessing services.
I personally like GetIt more, when I have multiple services where each depends on another service, I find it better to not clutter my widget tree with it.
I have for example dialog service, notification service, form service, router service, user API service, order API service, order service, auth service... I can simply have Dio registered as singleton, I can simply inject auth service into Dio auth interceptor.
Also Injectable package let's you define filters, so you can inject service A in release mode and mock service when you test something, for example.
Some of it might be overkill, but in the end I find it cleaner than using multiple Providers.
Hey @leoshusar, Can I support flavors in retrofit for Flutter Packages? I want to create a separate package for the ApiProvider (which will use retrofit with different base URLs based on the running env).
Hey @leoshusar, Can I support flavors in retrofit for Flutter Packages? I want to create a separate package for the ApiProvider (which will use retrofit with different base URLs based on the running env).
In other words, can I use the injectable configuration in my main project, while using retrofit generation in a different package?
Checkout my desirable folder scheme:

You want to import those packages via pubspec? Then it's unfortunately not supported now, but there is open issue for that even with some PoC. However the author is currently not that active, so who knows when it will be supported.
But you can put your injections from other packages into main @module
class:
@module
abstract class RegisterModule {
ApiProvider get apiProvider => ApiProviderImpl();
}
Just a detail: you usually name abstract classes without any suffix like Abs
(just ApiProvider
) and then concretize implementations, like MockApiProvider
- or sometimes Impl
(brought from Java world I think) :)
But of course do as you like.
Putting my packages into the main module sounds as a really good ides. I also take your advice for abstract names convention. Thanks a lot. π
Hey @leoshusar! I have a question regarding your simple example project. I can see that you chose to use factory instead of singleton in the rest_user_service.dart. Why is that?
In my case, I'm getting the app access token from an external server. After getting the access token, I need to update the Dio I'm passing to my RestApiProvider. In that case, I need the RestApiProvider update to reflect all its pointers. In other words, I need it to be a singleton.
How do I convert your rest_user_service.dart class from factory to singleton using retrofit? Any ideas for a better solution? Thanks in advance π
Hi, changing the annotation @Injectable
to @Singleton
should be enough.
The @factoryMethod
annotation just tells injectable to use it for constructing the object.
I'm using non singleton API services because they usually don't need to hold any state.
For auth I have AuthService
- this one is registered as singleton - and then use Dio interceptor for inserting token from auth service to all requests.
In RegisterModule
something like:
@singleton
Dio dio(AuthService authService) {
final dio = Dio()
..interceptors.add(AuthInterceptor(authService));
return dio;
}
Hi @leoshusar thanks for the great answer, again.
What is the purpose of AuthService? Is it only responsible holding the token state? Or does it also support other functionalities like retrieving new token in case of expiration?
According to this StackOverflow post, the interceptor handles expired tokens.
Can you please provide me a link to an AuthService implementation?
You're welcome.
My auth service is an abstraction around platform specific OAuth2 flows (AppAuth on Android, new window on web...).
It has methods for login, logout, it takes care of saving credentials and tracking token expiration, and then finally Future<String> getToken()
which always gets fresh token (either current or new by calling refresh internally).
I don't like the SO answer which suggests tracking token status inside interceptor, it's Dio dependent and it should belong into the Auth service IMO.
I'm sure you can figure out your own auth implementation :)