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;
});
}
}
Related
I'm working on flutter application that one should support localisation for multiple languages. I used to create .arb file for the localisation but for the API data i couldn't apply arb files ?
Here two ways make localization.
Use ARB (Application Resource Bundle) files
manage from API
manage from Api
make API post method they past language code. they return all arb file data from the backend side.
and then store data in the local database and display all places you want display.
in Get Package you can extend the translations class and assign that class object to GetMaterialApp translations property,
You just need to get your Language Map files from API and pass to overrided keys function. For more please check doc https://pub.dev/packages/get#translations
API Response like
res= {
'en': {
'SEARCH_FOR_SERVICES_AND_PROFESSIONALS':
'Search for services and professionals',
'MEMBERSHIP': 'Membership',
'PURCHASE': 'Purchase',
'GET_STARTED': 'Get started',
}
}
GetMaterialApp(
title: Constants.appName,
translations: Localization(res), //Localization class
and localization class
class Localization extends Translations {
// final Map<String, Map<String, String>>? res;
// Localization(this.res);
#override
Map<String, Map<String, String>> get keys => res
I am new to Flutter and I am writing an app which requires users to login. I already have an API that provides me with a JWT. I then store this using the secure storage library.
Once the user gets the 200 OK with the token, I want to send another request to the server to retrieve the user's details and store it in a kind of singleton class, so I can use it through out the app until he logs out. There are so many buzzwords being thrown at me right now, Providers and BLoCs.
I was wondering what is the best practice to store the current logged in user details in the app? Previously I've been aware of storing the data in a singleton class. But now I'm not sure whether I should write a singleton class or a Provider? (I'm still rusty on my knowledge about how Providers and BLoCs work).
Start Simple
A simple starting point to store the data, which i assume is the form of key/value pairs, would be using shared preferences.
What are Shared Preferences
The main use of Shared Preferences is to save user preferences, settings, maybe data (if not too large) so that next time the application is launched, these pieces of information could be retrieved and used.
How to use
Set an instance variable of Shared Preferences and store the value with a key identifier.
Any time you want to retrieve the value when the app starts, just get it using the key. See example below of setting and getting a stored username.
Future<bool> setUserName(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString("username",value);
}
Future<String> getUserName(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString("username");
}
Installation
To installed shared preferences, add this to your package's pubspec.yaml file:
dependencies:
shared_preferences: ^0.5.7+3
More information on the package page.
Solution 1
This is how my app stores data:
Create a new dart file for storing "global" variables like so, and store all the "session" data there:
class SessionData{
String encryptedUserId;
String encryptedPassword;
String encryptedToken;
int userId;
}
SessionData globalSessionData;
//Having a clear function is pretty handy
void clearSessionData(){
globalSessionData = new SessionData();
}
And if you named the file for example global.dart import it, read and write nonpermanent data to it (persist until the app is closed):
import 'global.dart' as global;
global.globalSessionData = ...;
//If you want to clear it:
global.clearSessionData();
Solution 2
If you want to persist the data even if the application closes there are two ways to do that:
Solution 2a
Use sharedPrefences to store and retrieve key-data values on the user's device:
import 'package:shared_preferences/shared_preferences.dart';
final SharedPreferences prefs = await SharedPreferences.getInstance();
//Write different types of data
await prefs.setBool("male", true);
await prefs.setDouble("key", 7.0);
await prefs.setString("username", "Mr. User");
//Retrive different types of data
prefs.getBool("male");
prefs.getDouble("key");
prefs.getString("username");
Solution 2b
Use sqflite to store advanced data structures, as it is a fully-featured database.
Flutter has a great tutorial on using SQLite with flutter. Although if you go with this type of solution you must know a little SQL.
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!
I have a small flutter app using redux for dart.
I am currently using redux_thunk for asynchronous actions and it works very well. Except that my async actions so far only contain dummy code like this:
void action(Store<String> store) async {
final searchResults = await new Future.delayed(
new Duration(seconds: 1),
() => "Search Results",
);
store.dispatch(searchResults);
}
Now I would like to call a method in a service (e.g. Firestore) but the thunk actions only take the store as an argument. So how would I pass the service into the actions?
Was wondering since I couldn't find any examples of a good pattern.
I found this: https://github.com/brianegan/flutter_architecture_samples/blob/master/firestore_redux/lib/middleware/store_todos_middleware.dart
But it has all async actions as part of the middleware which I don't particularly like.
I am using flutter to exchange firestore data from few devices.
If I use StreamBuilder everything works fine, but I do not like mixing business logic with UI. I would prefer using BLOC as pattern using flutter_bloc plugin.
But flutter_bloc works in this way:
Steps:
Event ------------------------> New data BUT NO NEW UI EVENT
Async Request
Async Response
State (mapEventToState)-------> ¿How do I get the new state?
As far as I do not have "UI Event" because firestore data is being updated from another device, I can not update State.
I could use something like this on the bloc constructor:
Stream<QuerySnapshot> query;
QuedadaBloc(){
query = Firestore.instance.collection('my_collection').snapshots();
query.listen((datos){
dispatch(Fetch()); // send fictitious UI event
});
}
But I think this is not the right way.
¿Any suggestion?
Many thanks.
J. Pablo.
The recommended way while using Flutter, Bloc and Firestore is to have the repository layer provide a stream of data from Firestore which can be subscribed by the Bloc in Bloc Constructor (or any other function; see this example).
Then, based on the changes in the stream, dispatch events when you receive new data from Firestore in the stream. The Bloc can handle the triggered dispatch event to change the State of the Application in a similar way when the changes in UI trigger the state change.
class SampleBloc extends Bloc<SampleEvent, SampleState> {
final FirestoreRepo _firestoreRepo;
StreamSubscription<?> _firestoreStreamSubscription;
SampleBloc({#required FirestoreData firestoreData})
: assert(firestoreRepo != null),
_firestoreRepo = firestoreRepo;
// instead of '_mapXEventToState', the subscription can also be wired in 'SampleBloc' constructor function.
Stream<TimerState> _mapXEventToState(XEvent xEvent) async* {
// Runs a dispatch event on every data change in Firestore
_firestoreStreamSubscription = _firestoreRepo.getDataStream().listen(
(data) {
dispatch(YEvent(data: data));
},
);
}
References:
Comment 1 and Comment 2 by Felix Angelov (felangel), flutter_bloc library creator in Bloc Gitter Chat