In my app I am using sembast db and I'm trying to adapt the AppDatabase singleton class to be platform dependent and use sambast_web when app is running on the web.
I'm trying to use the same pattern I used for user location so I created:
A stub with a getter method.
An abstract class AppDatabase to have a conditional package import for the device and web packages when importing the stub , and in the class factory method returns the stub getter method.
A class AppDatabaseDevice and a class AppDatabaseWeb which both implement AppDatabase.
When running the app dough I get an error:
Compiler message:
lib/fixit_shop_app/database/app_database_switcher.dart:50:28: Error: Method not found: 'getAppDatabase'.
factory AppDatabase() => getAppDatabase();
^^^^^^^^^^^^^^
Target kernel_snapshot failed: Exception: Errors during snapshot creation: null
Failed to build bundle.
Error launching application on iPad (6th generation).
Now, it's quite strange as when writing the code for the abstract class factory it finds getAppDatabase() an throws no error.
After twiddling a bit with the code I nailed down the problem being the conditional import of the stub import.
If I import it without conditional import as
import 'package:fixit_shop_flutter/fixit_shop_app/database/app_database_stub.dart';
then I get no error, but then again I do need the conditional import..
Can you spot why conditional import is failing for this class and works for the other?
As always thank you very much for your time and help.
These are the the AppDatabase methods:
Stub:
import 'app_database_switcher.dart';
AppDatabase getAppDatabase() => throw UnsupportedError(
'Cant get AppDatabase if not loading the right package');
Abstract class:
import 'dart:async';
import 'package:sembast/sembast.dart';
import 'app_database_stub.dart'
if (dart.library.io) 'package:sembast/sembast.dart'
if (dart.library.js) 'package:sembast_web/sembast_web.dart';
abstract class AppDatabase {
// Singleton instance
static final AppDatabase _singleton;
// Singleton accessor
static AppDatabase get instance => _singleton;
// Completer is used for transforming synchronous code into asynchronous code.
Completer<Database> _dbOpenCompleter;
// A private constructor. Allows us to create instances of AppDatabase
// only from within the AppDatabase class itself.
// Sembast database object
Database _database;
// Database object accessor
Future<Database> get database async {
// // If completer is null, AppDatabaseClass is newly instantiated, so database is not yet opened
return _dbOpenCompleter.future;
}
factory AppDatabase() => getAppDatabase();
}
And this are user location methods:
Stub:
import 'package:fixit_shop_flutter/fixit_shop_app/platform_user_location/user_location_switcher.dart';
UserLocation getUserLocation() =>
throw UnsupportedError('user_location_stub error');
Abstract class:
import 'package:fixit_shop_flutter/fixit_shop_app/platform_user_location/user_location_stub.dart' // Version which just throws UnsupportedError
if (dart.library.io) "package:fixit_shop_flutter/fixit_shop_app/platform_user_location/user_location_device.dart"
if (dart.library.js) "package:fixit_shop_flutter/fixit_shop_app/platform_user_location/user_location_web.dart";
abstract class UserLocation {
// methods to be used
Future<Map<String, dynamic>> getPosition() async {
Future<Map<String, dynamic>> position;
return position;
}
factory UserLocation() => getUserLocation();
}
Found the error. Indeed it was the conditional import. I was importing the diverse sembast packages instead of the device and web class implementations..
import 'package:fixit_shop_flutter/fixit_shop_app/database/app_database_stub.dart'
if (dart.library.io) 'package:fixit_shop_flutter/fixit_shop_app/database/app_database_device.dart'
if (dart.library.js) 'package:fixit_shop_flutter/fixit_shop_app/database/app_database_web.dart';
Related
The flutter package easy_localization has an extention method on the BuildContext which looks like this:
Locale get deviceLocale => EasyLocalization.of(this)!.deviceLocale;
I wish to mock this using Mocktail.
I tried:
import 'package:mocktail/mocktail.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:test/test.dart';
class MockBuildContext extends Mock implements BuildContext {}
void main() {
test("Sample test", () {
final context = MockBuildContext();
when(() => context.deviceLocale).thenReturn(const Locale('en', 'US'));
expect(Locale('en', 'US'), context.deviceLocale);
});
}
However this throws an error: type 'Locale' is not a subtype of type '_EasyLocalizationProvider?'. I am assuming this happens because we are (unsuccessfully) trying to mock an Extension method and that the original method is called instead.
How can I mock the extension method to get the desired result?
I'm using injectable and get_it in flutter dart (following the very popular Reso coder)
I have a simple abstract class:
import 'package:injectable/injectable.dart';
//#injectable
abstract class IRandomQuantityRepository {
Future<int> getRandomQuantity();
}
and I have two simple concrete implementations of it:
import 'package:injectable/injectable.dart';
#dev
#injectable
class DevRandomQuantityRepository implements IRandomQuantityRepository {
const DevRandomQuantityRepository();
#override
Future<int> getRandomQuantity() async => 90;
}
and
import 'dart:math';
import 'package:injectable/injectable.dart';
#prod
#injectable
class RandomQuantityRepository implements IRandomQuantityRepository {
const RandomQuantityRepository();
#override
Future<int> getRandomQuantity() async => Random().nextInt(100);
}
Lastly, I have an injection.dart:
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:moontreeapp/injection.config.dart';
final GetIt getIt = GetIt.instance;
#InjectableInit(preferRelativeImports: false)
void configureInjection(String env) {
$initGetIt(getIt, environment: env);
}
abstract class Env {
static const prod = 'prod';
static const dev = 'dev';
}
besides all that I have a bloc that wants to use stuff:
#injectable
class RandomQuantityBloc
extends Bloc<RandomQuantityEvent, RandomQuantityState> {
final IRandomQuantityRepository _quantityFacade; // notice this final...
doesn't that look good? I think so. So then I run this command to make the generated code flutter pub run build_runner watch
But I get a message:
[RandomQuantityBloc] depends on unregistered type [IRandomQuantityRepository]... Did you forget to annotate the above class(s) or their implementation with #injectable?
or add the right environment keys?
Ok, so that's cool, lets add it to the interface:
import 'package:injectable/injectable.dart';
#injectable // <-- added
abstract class IRandomQuantityRepository {
Future<int> getRandomQuantity();
}
but then I get a new error:
> [IRandomQuantityRepository] is abstract and can not be registered directly!
> if it has a factory or a create method annotate it with #factoryMethod
> 14 │ abstract class IRandomQuantityRepository {
> │ ^^^^^^^^^^^^^^^^^^^^^^^^^
In the past I've handled dependency injection manually, so I'm new to these packages, what am I missing here?
Besides all that, the real issue is that I can't switch the injection based on the environment. I can use get_it to get a concrete dependency but not one based on environment like in this test:
/// has no effect:
configureInjection(Env.dev);
/// gets prod version:
final devRandomQuantity = getIt<RandomQuantityRepository>();
So something about this whole set up isn't configuring the Injections correctly... What am I missing?
One final thing that might be useful is to see the generated code:
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// InjectableConfigGenerator
// **************************************************************************
import 'package:get_it/get_it.dart' as _i1;
import 'package:injectable/injectable.dart' as _i2;
import 'package:moontreeapp/application/quantity/bloc/randomquantity_bloc.dart'
as _i5;
import 'package:moontreeapp/domain/quantity/i_randomquantity_repository.dart' as _i6;
import 'package:moontreeapp/infrastructure/quantity/dev_randomquantity_repository.dart'
as _i3;
import 'package:moontreeapp/infrastructure/quantity/mock_randomquantity_repository.dart'
as _i4;
import 'package:moontreeapp/infrastructure/quantity/randomquantity_repository.dart'
as _i7;
const String _dev = 'dev';
const String _prod = 'prod';
// ignore_for_file: unnecessary_lambdas
// ignore_for_file: lines_longer_than_80_chars
/// initializes the registration of provided dependencies inside of [GetIt]
_i1.GetIt $initGetIt(_i1.GetIt get,
{String? environment, _i2.EnvironmentFilter? environmentFilter}) {
final gh = _i2.GetItHelper(get, environment, environmentFilter);
gh.factory<_i3.DevRandomQuantityRepository>(
() => _i3.DevRandomQuantityRepository(),
registerFor: {_dev});
gh.factory<_i5.RandomQuantityBloc>(
() => _i5.RandomQuantityBloc(get<_i6.IRandomQuantityRepository>()));
gh.factory<_i7.RandomQuantityRepository>(() => _i7.RandomQuantityRepository(),
registerFor: {_prod});
return get;
}
Do I put #injectable on abstract classes or not?
ok, so I guess injectable can't see what the class implements so you have to make it explicit. also, I missed that #dev isn't built in, you have to make it.
So this is the proper way to use the decorations
#Environment('dev')
#Injectable(as: IRandomQuantityRepository)
class DevRandomQuantityRepository implements IRandomQuantityRepository {
const DevRandomQuantityRepository();
#override
Future<Either<QuantFailure, Quantity>> getRandomQuantity() async =>
right(Quantity(Amount(900.0001), Decimal(4)));
}
and that way this does work:
configureInjection(Env.dev);
final mockRandomQuantity = getIt<IRandomQuantityRepository>();
await mockRandomQuantity.getRandomQuantity();
// 90
I have two Android specific Flutter plugins. They are for two custom devices to access the same hardware with different platform specific SDKs.
I have successfully implemented both as Flutter plugins. I want use these in Flutter application and use the plugin based on the device.
I have created a common abstract class to expose same API but the flutter plugin class has all static methods which doesn't allows to implement a common interface.
How can we expose a common dart implement from a plugin and use it interchangeably.
For an example let say we have this abstract class as the common interface,
abstract class Pluggable{
void plug();
}
The plugin class which is generated by Flutter create is,
import 'dart:async';
import 'package:flutter/services.dart';
class MyPlugin {
static const MethodChannel _channel = MethodChannel('my_plugin');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<void> plug() async {
await _channel
.invokeMethod('plug');
}
}
The way of Flutter plugins to have static methods which can not be overridden.
The problem is that your flutter plugin class has abstract methods. There are many ways to do this, from using a proper dependency injection framework to have the right type injected, to using a Provider or other InheritedWidget wrapper to hold the instance of the right plugin class and expose it in a common way.
However, the simplest way is to use a Singleton - assuming that this is something that that gets instantiated right at the beginning of the app running and is used throughout.
The way I'd recommend is to add a static initializer to your singleton. See the example below.
// this declares the abstract class.
abstract class MyAbstractClass {
static void initializeWith(MyAbstractClass instance) {
_instance = instance;
}
// this could introduce a potential bug if you don't initialize
// or try to do it multiple times, so make sure you do that
// properly exactly once.
static late final MyAbstractClass _instance;
static MyAbstractClass get instance => _instance;
// methods to show how this works
void method1();
void method2();
// an example of how to call directly from the class
static void doMethod1() => _instance.method1();
}
// one simple implementation
class MyClass1 implements MyAbstractClass {
#override
void method1() => print(1);
#override
void method2() => print(2);
}
// another simple implementation
class MyClass2 implements MyAbstractClass {
#override
void method1() => print("a");
#override
void method2() => print("b");
}
// and in practice, you simply have to initialize and then
// use however you'd like.
void main() {
// MyAbstractClass.initializeWith(MyClass1());
// MyAbstractClass.doMethod1();
// MyAbstractClass.instance.method2();
MyAbstractClass.initializeWith(MyClass2());
MyAbstractClass.doMethod1();
MyAbstractClass.instance.method2();
}
You'd have to convert all of your static methods to members but that should be as simple as removing any static references and removing the static keywords.
all the examples, I have seen, initialize ObjectBox in a State(less/full)Widget. I am using a layered architecture (currently refactoring to DDD) and wonder, how to inject my ObjectBox properly.
In my repository, I inject the data sources using the injectable and the getit packages with
#injectable
#LazySingleton (as: IJournalsRepository)
class JournalsRepository implements IJournalsRepository {
final JournalsRemoteDataSource journalsRemoteDataSource;
final JournalsLocalDataSource journalsLocalDataSource;
JournalsRepository(this.journalsLocalDataSource, this.journalsRemoteDataSource);
Those packages then create an instance of JournalsRemoteDataSource and of JournalsRemoteDataSource and inject it into the repository.
The ObjectBox example shows for initialization
class _MyHomePageState extends State<MyHomePage> {
Store? _store;
#override
void initState() {
super.initState();
openStore().then((Store store) => _store = store;);
}
#override
void dispose() {
_store?.close(); // don't forget to close the store
super.dispose();
}
}
So I am lacking an idea on how an injector could initialize ObjectBox or how I could access the objectBox object from within the injected JournalsRemoteDataSource if I would initialize objectBox in MyApp() (which is upstream to the HomePage)
PS: reopening the box in JournalsRemoteDataSource on every read/write event has a very poor performance
========== UPDATE ==========
supplementing my comment to #vaind
I have found your answer on this similar question in the meantime (not sure why I did not see it, initially). I hope to get this approach working here, too. However, I have still issues initializing the store. My prototype comes from Firestore and looks like this:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:injectable/injectable.dart';
#module
abstract class FirebaseInjectableModule {
#lazySingleton
FirebaseAuth get firebaseAuth => FirebaseAuth.instance;
}
though I do not understand where the getter firebaseAuth comes from and haven't found any explanation, yet. Anyways, I adapted that to
import 'package:injectable/injectable.dart';
import 'package:objectbox/objectbox.dart';
import 'package:test/objectbox.g.dart';
#module
abstract class ObjectboxInjectableModule {
#lazySingleton
Future<Store> get store async => await openStore();
}
and use this with
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
Besides final Store _store being grey in IntelliJ (unused variable), I receive the error
You tried to access an instance of Store that is not ready yet
'package:get_it/get_it_impl.dart':
Failed assertion: line 404 pos 9: 'instanceFactory.isReady'
So following another answer of vaind, I implemented this as follows. My architecture follows a merge of Reso Coder's DDD and Clean Architecture tutorials. Basically it is DDD with the local/remote data source layer of Clean Architecture.
INFRASTRUCTURE directory
abstract data sources
abstract class ILocalDataSource {
Future<JournalDto> getJournal(int id);
Future<void> storeJournal(JournalDto record);
}
abstract class IRemoteDataSource {
Future<JournalDto> getJournal(int problemClassId);
}
data source implementation
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
injectable module in infrastructure/core
#module
abstract class ObjectBoxInjectableModule {
#preResolve // <<<<<<<<<<<<< needed for async init
#lazySingleton
Future<Store> get store async => await openStore();
}
And now the trick to get it work: My later errors where caused by an injector init not yet finished. After changing injection.dart in the root folder to a Future and awaiting the call in main(), it worked. injection.dart now looks like this:
final GetIt getIt = GetIt.instance;
#injectableInit
Future<void> configureInjection(String env) async {
$initGetIt(getIt, environment: env);
}
I don't have experience with packages get_it & injectable, but from the docs, I think the following alternatives would work. Using get_it directly, not sure about the right way to achieve the same with injectable (generator for get_it) but I guess if you're familiar with it you can configure it to generate the same code.
Alternative A, lazy (async) singleton
GetIt.I.registerSingletonAsync<Store>(openStore);
Alternative B, setup in main(), probably preferrable
change your main to sth like:
void main() async {
GetIt.I.registerSingleton<Store>(await openStore());
runApp(MyApp());
}
Note: Looks like get_it provides a way to reset, which would result in reopening the same store. To avoid issues if you use that, you'd also need to implement a version of get_it's dispose that calls store.close().
I wrote the following code using freezed with the intention of managing the loading state when the sign-in button is pressed on the sign-in page.
//sign_in_page_state.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:state_notifier/state_notifier.dart';
part 'sign_in_page_state.freezed.dart';
#freezed
abstract class SignInPageState with _$SignInPageState {
const factory SignInPageState.noError() = _NoError;
const factory SignInPageState.error(String errorText) = _Error;
const factory SignInPageState.loading(String loadingText) = _Loading;
}
class SignInPageModel extends StateNotifier<SignInPageState>{
SignInPageModel(SignInPageState state):super(state);
void startSignIn(){
state=SignInPageState.loading("now signing in,please wait a minute.");
}
void successSignIn(){
state=SignInPageState.noError();
}
void errorSignIn(){
state=SignInPageState.error("failed to sign in.");
}
}
//signin_page.dart
class SignInPage extends StatefulWidget {
final String title = 'sign in';
static Widget create(BuildContext context,){
return StateNotifierProvider<SignInPageModel, SignInPageState>(
create:(_)=>SignInPageModel(SignInPageState.noError(),),
child:SignInPage(),
);
}
At this time, the following error will appear.
//error message
'SignInPageModel' doesn't conform to the bound 'StateNotifier<SignInPageState>' of the type parameter 'Controller'.
Try using a type that is or is a subclass of 'StateNotifier<SignInPageState>'.
No matter how I look at it, SignInPageModel is a subclass of StateNotifier<SignInPageState>.
and I also define and use UploadMovieState in much the same way as SignInPageState, but I don't get this error at all.
so I can't figure out the cause at all.
What could be the cause?
Since the State of SignInPage was declared as follows, it was caused by the same name 'SignInPageState'.
I'm sorry I asked a really silly question lol
class SignInPageState extends State<SignInPage> {
//...