Is there any way we can inject provider dependancy to normal class in flutter? - flutter

I have one normal dart class in which I want to provide two provider dependency.
So I can access that class though out of my application. I can pass that dependency from the build method of the widget and then I can use this class but I don't want to do that, like 100 times I have to pass that dependency if I used that class 100 times.
I also want to use this class from every lifecycle of flutter as it's generating different types of events for application.
I also want to initialize genrateUserProerties() method only once when the dependency is ready and when the user opens the application.
This is initialized before any provider initializes and it's not always used from the method where we have context available.
I need a way to provide that dependency in a way we can initialize genrateUserProerties() only once.
User _user; and BrandCofiguration _activeBrand; I need these two be pass here when it's ready.
User _user; and BrandCofiguration _activeBrand; both are coming from two different Providers when I received a valid response from the server.
class FireBaseAnalyticsBase {
static FirebaseAnalytics _analytics;
static FirebaseAnalyticsObserver _observer;
**User _user;**
BuildContext _context;
**BrandCofiguration _activeBrand;**
int _seconds;
Stopwatch _stopwatch;
String _eventName;
Map<String, dynamic> _userProperties = {};
bool _isTimeTrackEvent;
FireBaseAnalyticsBase(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider) {
this._context = context;
_analytics = FirebaseAnalytics();
_observer = FirebaseAnalyticsObserver(analytics: _analytics);
_activeBrand = brandSelectionProvider.activeBrand;
_user = userProvider.authenticatedUser;
if (_user != null) {
genrateUserProerties();
}
}
void startFirebaseEventWithoutTime(String eventName) {
this._eventName = eventName;
_isTimeTrackEvent = false;
logFirebaseEvent();
}
void startFireBaseEventWithTime(String eventName) {
_stopwatch = Stopwatch();
_stopwatch.start();
_isTimeTrackEvent = true;
_eventName = eventName;
}
void stopFireBaseTimeEvent() {
_stopwatch.stop();
_seconds = (_stopwatch.elapsedMilliseconds / 1000) as int;
_stopwatch.reset();
logFirebaseEvent();
}
Future<void> logFirebaseEvent() async {
if (_isTimeTrackEvent) {
_userProperties
.addAll({FirebaseAnalyticsEnum.time_spent.value: _seconds});
}
print("firebase test");
await _analytics.logEvent(
name: _eventName,
parameters: _userProperties,
);
}
Future<void> genrateUserProerties() async {
print("firebase properties initilize");
var _packageInfo = await PackageInfo.fromPlatform();
_userProperties = {
FirebaseAnalyticsEnum.user_id.value: _user.id.toString(),
FirebaseAnalyticsEnum.platform.value: Platform.operatingSystem,
FirebaseAnalyticsEnum.device_language.value:
Localizations.localeOf(_context).languageCode,
FirebaseAnalyticsEnum.application.value: _packageInfo.appName,
FirebaseAnalyticsEnum.current_api.value: Config.CURRENT_API,
FirebaseAnalyticsEnum.device_type.value: _user.id.toString(),
FirebaseAnalyticsEnum.app_version.value: Config.CURRENT_VERSION,
FirebaseAnalyticsEnum.is_admin.value: _user.isAdmin,
FirebaseAnalyticsEnum.is_educator.value: _user.educator,
FirebaseAnalyticsEnum.is_brand_ambassador.value: _user.brandAmbassador,
FirebaseAnalyticsEnum.salon_role.value: _user.salongroup,
FirebaseAnalyticsEnum.brand.value: _activeBrand.brandName,
FirebaseAnalyticsEnum.school_role.value: _user.schoolgroup,
};
}
}

I think you should use Singleton pattern for these classes consume a lot of resources.
Example:
static FireBaseAnalyticsBase _instance;
static FireBaseAnalyticsBase getInstance(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider){
if(_instance == null){
_instance = FireBaseAnalyticsBase(context,userProvider,brandSelectionProvider);
}
return _instance;
}
Or if you passing BuildContext you can get Provider
Provider.of<>(context) in FireBaseAnalyticsBase's Constructor

Related

Is there a best practice to call a specific function every time before any function in Dart/Flutter?

I am working with singleton pattern, and I have managed the dependency and parameter injections like this:
LocalizationClientComponent._();
/// Singleton instance of the [LocalizationClientComponent] class.
static final instance = LocalizationClientComponent._();
static bool _injectedDependencies = false;
static bool _injectedParams = false;
late final DeviceInfoRepository _deviceInfoRepository;
late final MapapiRepository _mapapiRepository;
late final SensorsRepository _sensorsRepository;
late final LocationRepository _locationRepository;
late final BluetoothRepository _bluetoothRepository;
late final WifiRepository _wifiRepository;
void injectDependencies({
required DeviceInfoRepository deviceInfoRepository,
required MapapiRepository mapapiRepository,
required SensorsRepository sensorsRepository,
required LocationRepository locationRepository,
required BluetoothRepository bluetoothRepository,
required WifiRepository wifiRepository,
}) {
if (_injectedDependencies) return;
_deviceInfoRepository = deviceInfoRepository;
_mapapiRepository = mapapiRepository;
_sensorsRepository = sensorsRepository;
_locationRepository = locationRepository;
_bluetoothRepository = bluetoothRepository;
_wifiRepository = wifiRepository;
_injectedDependencies = true;
}
Then the same thing for parameters:
void injectParams({
required String email,
required String password,
required Duration postFrequency,
required int sensorListLength,
}) {
if (_injectedParams) return;
_postFrequency = postFrequency;
_sensorListLength = sensorListLength;
_telemetryEvent = TelemetryEvent(_sensorListLength);
_injectedParams = true;
}
After that, I made this method to control whether params and dependencies are injected:
void _handleInitError() {
final StringBuffer errorMessage = StringBuffer();
if (!_injectedDependencies) {
errorMessage
..write(
'Dependencies are not injected. Use injectDependencies() before using any other methods.',
)
..write('\n');
}
if (!_injectedParams) {
errorMessage.write(
'Parameters are not injected. Use injectParams() before using any other methods.',
);
}
if (errorMessage.isNotEmpty) {
throw Exception(errorMessage.toString());
}
}
Now, I am adding _handleInitError() to all other methods like this:
Future<bool> openAppSettings() async {
_handleInitError();
return _locationRepository.openAppSettings();
}
Future<bool> openLocationSettings() async {
_handleInitError();
return _locationRepository.openLocationSettings();
}
Future<bool> requestLocationPermission() {
_handleInitError();
return _locationRepository.requestLocationPermission();
}
Future<bool> activateLocationService() async {
_handleInitError();
return _locationRepository.activateLocationService();
}
Future<bool> turnOnWifi() async {
_handleInitError();
return _wifiRepository.turnOnWifi();
}
Is there a better way than adding all methods to _handleInitError()?
Is there a way to automatically execute this method before any method?
You could define a base function that your functions get passed as parameter and before each call you do your _handleInitError
void call(Function f){
_handleInitError();
f.call();
}
Call it like this
call(requestLocationPermission);

How to Mock the Objectbox in Flutter?

I am writing a bloc test in which I want to test GroupBloc and I need to mock the Objectbox database.
How can i do this?
Objectbox:
class ObjectBox {
/// The Store of this app.
late final Store store;
/// A Box of groups.
late final Box<GroupEntity> groupsBox;
late final Box<LessonEntity> lessonBox;
late final Box<StudentEntity> studentBox;
ObjectBox._create(this.store) {
groupsBox = Box<GroupEntity>(store);
lessonBox = Box<LessonEntity>(store);
studentBox = Box<StudentEntity>(store);
}
/// Create an instance of ObjectBox to use throughout the app.
static Future<ObjectBox> create() async {
final store = await openStore();
return ObjectBox._create(store);
}
}
My Repository:
class GroupRepository {
GroupDao dao = GroupDao();
static BaseConverter<GroupEntity, Group> converter = GroupDbConverter();
Future<List<Group>> getGroupList() async {
final getGroupsList = await dao.getAll();
final groupsList = converter.listInToOut(getGroupsList);
return groupsList;
}
}

How to save data of type bool in shared_preferences flutter

I created a separate calss page to working with shared preferences from all the different application pages. Save or edit data. I can save String data with ease, but I am facing a problem saving data of type bool. I try to save data of type bool to store the status of the user logged in or not. I searched for solutions for a long time, but couldn't find.
full code:
import 'package:shared_preferences/shared_preferences.dart';
class MyPreferences {
static const ID = "id";
static const STATE = "state";
static final MyPreferences instance = MyPreferences._internal();
static SharedPreferences _sharedPreferences;
String id = "";
String state = "";
MyPreferences._internal() {}
factory MyPreferences() => instance;
Future<SharedPreferences> get preferences async {
if (_sharedPreferences != null) {
return _sharedPreferences;
} else {
_sharedPreferences = await SharedPreferences.getInstance();
state = _sharedPreferences.getString(STATE);
id = _sharedPreferences.getString(ID);
return _sharedPreferences;
}
}
Future<bool> commit() async {
await _sharedPreferences.setString(STATE, state);
await _sharedPreferences.setString(ID, id);
}
Future<MyPreferences> init() async {
_sharedPreferences = await preferences;
return this;
}
}
Can somebody help me to make bool data.
thank you
Just add a couple methods to your class.
void updateLoggedIn(bool value) {
_sharedPreferences.setBool('logged_in', value);
}
bool isLoggedIn() => _sharedPreferences.getBool('logged_in') ?? false;
Then on login just run
MyPreferences.instance.updateLoggedIn(true)
And the same thing passing in false on logout.
Then whenever you want to check logged in status just run
if(MyPreferences.instance.isLoggedIn()) {
// whatever needs to happen
}

Flutter api login using riverpod

I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}
Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})
Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }

SharedPreferences.getInstance() is always returning null

Coming from Object Oriented Programming Background, I planned on making a dedicated Settings Class to store certain basic data about the app.
I planned on starting with saving the theme of the application using SharedPreferences and LocalStorage.
However, SharedPreferences.getInstance() always seems to be returning null.
I have tried simply running, running in Debug mode, having a separate async method to load the SharedPreferences and returning a Future which is unwrapped using .then(). I can't seem to figure out why I am always getting null from SharedPreferences.getInstance() in the AppSettings.getInstance() method that I have written.
import 'package:shared_preferences/shared_preferences.dart';
import 'package:localstorage/localstorage.dart';
import 'package:flutter/material.dart';
class AppSettings {
// Singleton Instance
static AppSettings _appSettings;
// For First Launch Recognition
bool _initialize;
// Storage instances for persistent settings storage
static SharedPreferences _prefs;
static LocalStorage _dayColors = new LocalStorage('_dayColors');
static LocalStorage _nightColors = new LocalStorage('_nightColors');
// App Settings
bool _nightTheme;
Color _dayBgColor;
Color _primaryDayColor;
Color _secondaryDayColor;
Color _accentDayColor;
Color _nightBgColor;
Color _primaryNightColor;
Color _secondaryNightColor;
Color _accentNightColor;
static AppSettings getInstance() {
SharedPreferences.getInstance().then((prefs) => _prefs = prefs);
_appSettings ??= AppSettings._();
return _appSettings;
}
///
/// Initialize App Settings
///
AppSettings._() {
_checkIfFirstLaunch();
if (_initialize) {
_loadDefaultSettings();
_saveSettings();
} else {
_loadSettings();
}
}
_checkIfFirstLaunch() {
try {
_initialize = _prefs.getBool("_initialize");
} catch (e) {
_initialize = true;
}
}
_loadSettings() {
_nightTheme = _prefs.getBool("_nightTheme");
_dayColors.ready.then((_) => _loadDayColors());
_nightColors.ready.then((_) => _loadNightColors());
}
_loadDefaultSettings() {
_nightTheme = false;
_dayBgColor = Colors.white;
_primaryDayColor = Colors.blue;
_secondaryDayColor = Colors.lightBlue;
_accentDayColor = Colors.blueAccent;
_nightBgColor = Colors.black54;
_primaryNightColor = Colors.green;
_secondaryNightColor = Colors.lightGreen;
_accentNightColor = Colors.amber;
}
_saveSettings() {
_prefs.setBool("_nightTheme", _nightTheme);
_dayColors.ready.then((_) => _saveDayColors());
_nightColors.ready.then((_) => _saveNightColors());
}
}
SharedPreferences.getInstance() should return SharedPreferences singleton instance. It keeps returning null.
Your function is async and your callback (then) executes after of the return of getInstance(). You must change your function to use await and get the value of SharedPreferences.getInstance() instead use SharedPreferences.getInstance().then(...)
Look the documentation: https://pub.dev/documentation/shared_preferences/latest/shared_preferences/SharedPreferences/getInstance.html
Implementation of SharedPreferences.getInstance().
static Future<SharedPreferences> getInstance() async {
if (_instance == null) {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_instance = SharedPreferences._(preferencesMap);
}
return _instance;
}
Here is the code that worked based on Augusto's answer:
static Future<AppSettings> getInstance() async {
_prefs = await SharedPreferences.getInstance();
_appSettings ??= AppSettings._();
return _appSettings;
}