I'm new to Flutter and have just heard the BLoC concept from reading tutorials about Flutter. From this tutorial, I heard BLoC for the first time. But I also see a file called "Repository" in this article. Basically the data flow goes like this:
Web API --> Api Provider --> Repository --> BLoC --> Widget
What I don't understand is that why there's a need for the Repository layer, as when I look at the repository file, it's basically just returning the API Provider's Future result? I got curious and try to search further, and I see some of the people's coding patterns on the internet also has a Repository layer on it.
In the original article, the API Provider does everything. It calls the get request, it awaits for the Future resolve, it converts the JSON data into appropriate model, and return the model enclosed with Future.
class ApiProvider {
Future<ItemModel> fetchMovieList() async {
final response = await client.get("http://api.themoviedb.org/3/movie/popular?api_key=$_apiKey");
if (response.statusCode == 200)
return ItemModel.fromJson(json.decode(response.body));
else
throw Exception('Failed to load post');
}
}
class Repository {
ApiProvider _api = ApiProvider();
Future<ItemModel> fetchMovieList() => _api.fetchMovieList(); // why?
}
class Bloc {
Repository _repository = Repository();
final _moviesFetcher = PublishSubject<ItemModel>();
Observable<ItemModel> get allMovies => _moviesFetcher.stream;
fetchAllMovies() async {
ItemModel itemModel = await
_repository.fetchAllMovies();
_moviesFetcher.sink.add(itemModel);
}
}
Currently I modify it so that the Api Provider returns pure Future, where the Repository implement the .then() and convert the response into appropriate data, but I tend to avoid await because in React Native await causes the app to look unresponsive. I also move error checking into BLoC.
class ApiProvider {
Future fetchMovieList() => client.get("http://api.themoviedb.org/3/movie/popular?api_key=$_apiKey");
}
class Repository {
ApiProvider _api = ApiProvider();
Future<ItemModel> fetchMovieList() => _api.fetchMovieList().then(response => ItemModel.fromJson(json.decode(response.body));
}
class Bloc {
Repository _repository = Repository();
final _moviesFetcher = PublishSubject<ItemModel>();
Observable<ItemModel> get allMovies => _moviesFetcher.stream;
fetchAllMovies() async => _repository.fetchPopularMovies().then((response) => _moviesFetcher.sink.add(response))
.catchError((onError) => throw Exception("Failed to load post $onError"));
}
But still, I feel like this is a stretch to justify the need for this Repository layer. If I can, I want to make it like this:
class ApiProvider {
Future<ItemModel> fetchMovieList() => client.get("http://api.themoviedb.org/3/movie/popular?api_key=$_apiKey")
.then(response => ItemModel.fromJson(json.decode(response.body));
}
class Bloc {
ApiProvider _api = ApiProvider();
final _moviesFetcher = PublishSubject<ItemModel>();
Observable<ItemModel> get allMovies => _moviesFetcher.stream;
fetchAllMovies() async => _api.fetchPopularMovies().then((response) => _moviesFetcher.sink.add(response))
.catchError((onError) => throw Exception("Failed to load post $onError"));
}
and get rid of the Repository layer altogether. I'm not trying to say the Repository layer is unnecessary, but right now I don't know what pattern problem the Repository layer trying to solve. I just want to know why there's a Repository layer in the first place and what the real-world significant use case of Repository. I know this question may be flagged as a question that can trigger discussion instead of straight answers. But I believe there is some kind of narrowed answers for this question. I just can't find it when I tried to search on the internet (the search result got mixed up with other uses of "Repository" terms, like git and subversion).
Ok, forget about it. I found this excellent article that explains that basically Repository is to abstract where the data is coming from, whether it's from disk cache, cloud, or other source. The factory will decide what source to use based on the each source availability. The caller will just only need to go through one gate. Because the tutorial above has only one source (API/cloud), it looks useless to me at that moment.
Here is an excellent summary of the why. And it makes complete sense. This is from the BLoC documentation, where they detail a weather app tutorial that uses a Repository layer (see here for the full article).
"The goal of our repository layer is to abstract our data layer and
facilitate communication with the bloc layer. In doing this, the rest
of our code base depends only on functions exposed by our repository
layer instead of specific data provider implementations. This allows
us to change data providers without disrupting any of the
application-level code. For example, if we decide to migrate away from
metaweather, we should be able to create a new API client and swap it
out without having to make changes to the public API of the repository
or application layers."
I'm going for it!
Related
I am using the http package to call my API, but every request takes 8+ seconds to complete.
I have tried calling the same route via browser and postman and I get the response in less than a second. Also, I can assure that there is no issue with my internet connection.
class ApiRest {
Future<List<Product>> getProducts() async {
final apiResponse = await http.get(Uri.parse('some route'));
final resBody = jsonDecode(apiResponse.body);
return resBody['products']
.map<Product>((product) => Product.fromJson(product))
.toList();
}
}
Additionally, I tried applying the approach recommended by this answer, using the HTTP Client(), which didn't make a difference either.
Is there anything I am doing wrong or inefficient?
Lastly, the latest version of the http package for Flutter is currently 0.13.4. Do you think that the issue might be with this package? Or it might not be stable enough?
I'm looking for suggestions on how to handle loading things after login, and combine data from multiple endpoints without mixing too much stuff, specifically the following two things:
1) Login flow
After I get a successful login response with userId, I need to push HomeScreen() and load some initial data, from various providers.
Example:
// home_screen.dart
initState() {
super.initState();
initializeData();
}
Future <void> initializeData() {
var authenticationProvider = Provider.of<AuthenticationProvider>(context);
var accountProvider = Provider.of<AccountProvider>(context);
var albumProvider = Provider.of<AlbumProvider>(context);
var songProvider = Provider.of<SongProvider>(context);
await accountProvider.loadAccount(authenticationProvider.getLoggedInUser().id);
await albumProvider.loadAlbums();
await songProvider.loadSongsForAlbums(albumProvider.getAlbums())
}
This works, but feels ugly?
2) Cleaner data sharing
Imagine the API like this:
api/albums (model contains info about album)
api/albumpurchases (model contains which albumId and userId)
What would be the best way to get purchased albums of the logged in user?
I can think of 3 different ways, none of which seem good:
AlbumProvider having two arrays, albums[], and purchases[], and a method getAlbumByPurchase (String purchaseId) or getPurchaseForAlbum(String albumId) which then does .where() by Id and returns the item.
Having AlbumProvider and AlbumPurchaseProvider, then using ProxyProvider to combine the two.
I'm not sure how exactly would that be implemented, an example would be very appreciated!
Adding purchase property to an Album, then manually mapping it similarly to way #1
.
I've used this so far, and it seems great in the beginning but gets very ugly very quick since your subsequent Album HTTP responses everywhere would be lacking that purchase property, so I either need to get the purchase again, or I need to get it an Album object with the purchase from AlbumProvider based on an Id in response I got.
I have an app that follows MVC+service architecture. The service layer makes the http requests for rest APIs. However the response of the http requests change intermittently which cause my models to change or else random crashes in my app. SO to capture the change in these APIs I want to write some automated tests which can tell me exactly what changed. A sample test case is as following:
test("login_valid", () async {
final loginData = LoginData(
email: "abc#gmail.com",
password: "123"
);
final parameters = loginData.toJson();
var json = await httpService.post(parameters);
var loginResponse = LoginResponse.fromJson(json);
expect(loginResponse.status, "OK");
});
However, the above code throws SocketException upon run. I know this exception is thrown when INTERNET permission is not given in AndroidManifest.xml but I don't know how to set this for unit\widget tests.
P.S. I can't mock the service layer using mockit or similar framework because the whole point is to test my service layer which doesn't have any business logic but just provides network integration.
Any solution or suggestion will be really helpful. I am okay with other approaches to achieve the same intent also, if there are any.
Check this or the gihub issue discussion pointed there
https://timm.preetz.name/articles/http-request-flutter-test
I am after some best practise tips for developing my Flutter app.
Currently, I have an app with multiple pages and multiple plugins such as network connection, SQLite, Location etc.
Currently, on each page, I am creating a new instance of each plugin I need access to as shown below, and then using the plugin functionality.
final _secureStorage = FlutterSecureStorage();
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
What I want to achieve: I would like to possibly only create an instance of these classes once, and then be able to access the instance in all pages - something like Dependency Injection.
Currently, I am looking into InheritedWidget widget or the Provider package, however, I am not sure if they do what I am trying to achieve as I don't want to inherit or pass around widgets, I want to inject classes instances.
You could try out, the get_it package, since it is not tied to Flutter.
https://pub.dev/packages/get_it
The ioc_container may suit your case because it has specific documentation on how to use it with Firebase and firebase Messaging. It does exactly as you say: initialize Firebase asynchronously and store the instances as singletons with dependency injection.
Here is a snippet of the code from the documentation:
extension FlutterFireExtensions on IocContainerBuilder {
void addFirebase() {
//These factories are all async because we need to ensure that Firebase is initialized
addSingletonAsync(
(container) {
WidgetsFlutterBinding.ensureInitialized();
return Firebase.initializeApp(
options: container.get<FirebaseOptions>(),
);
},
);
addSingletonAsync(
(container) async => FirebaseAuth.instanceFor(
app: await container.getAsync<FirebaseApp>(),
),
);
addSingletonAsync(
(container) async => FirebaseFirestore.instanceFor(
app: await container.getAsync<FirebaseApp>(),
),
);
addSingletonAsync((container) async {
//Ensure we have already initialized Firebase
await container.getAsync<FirebaseApp>();
return FirebaseMessaging.instance;
});
}
}
I would like to put in a bit of infrastructure on my project to SaveChanges on my db context at the end of every request.
So I create a simple piece of Owin middleware
app.Use(async (ctx, req) => {
await req();
var db = DependencyResolver.Current.GetService<MyDbContext>();
await db.SaveChangesAsync();
});
This does not work and throws the error
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.
If I resolve the db before completing the request
app.Use(async (ctx, req) => {
var db = DependencyResolver.Current.GetService<MyDbContext>();
await req();
await db.SaveChangesAsync();
});
It doesn't throw an error but it also doesn't work (as in changes aren't saved to the db and viewing db in the debugger shows the DbSet's Local property throwing an InvalidOperationException about it being disposed.
I've tried with and without async, registering the middleware before and after autofac configuration (app.UseAutofacMiddleware(container)) and resolving the LifetimeScope directly from the Owin environment. All give me the same results.
I've done something like this before with Structuremap, but can't seem to figure the correct way to get Autofac to play nice.
Steven is right about the fact that you should not be committing on the request disposal because you cannot be sure if you really want to commit there, unless you abstract your UoW from DbContext and keep a success attribute there, checking if on disposal and conditionally commit.
For your specific question, there are two things to clarify.
DbContext or UoW need to be registered with InstancePerRequest()
Instead of using Owin middleware, you should use OnRelease(context => context.SaveMyChangesIfEverythingIsOk()) native Autofac API
For example, this is how it would look like for RavenDb
builder.Register(x =>
{
var session = x.Resolve<IDocumentStore>().OpenAsyncSession();
session.Advanced.UseOptimisticConcurrency = true;
return session;
})
.As<IAsyncDocumentSession>()
.InstancePerRequest()
.OnRelease(x =>
{
x.SaveChangesAsync();
x.Dispose();
});