How to disable admob for flutter web - flutter

i have a flutter app. It's working on web browsers and mobile devices. I have added ad mob for showing banner ads. But i cant run the project because of google_mobile_ads doesn't support web.
I'm starting google mobile ads if the current platform is a mobile device.
I have an export.dart like this:
export 'ad_mobile.dart' if (dart.library.html) 'ad_web.dart';
ad_mobile.dart:
import 'package:google_mobile_ads/google_mobile_ads.dart';
Future<dynamic> initAds() async {
await _initGoogleMobileAds();
}
Future<InitializationStatus> _initGoogleMobileAds() {
RequestConfiguration configuration = RequestConfiguration(
testDeviceIds: <String>[
testDeviceId,
],);
MobileAds.instance.updateRequestConfiguration(configuration);
return MobileAds.instance.initialize();
}
and ad_web.dart is:
import 'dart:developer';
Future initAds() async {
log('ADS DOES\'NT SUPPORTED FOR WEB PLATFORMS');
}
When i run the app on Chrome, app starts but stuck at white screen. And i get this error on debug console:
Error: MissingPluginException(No implementation found for method _init on channel plugins.flutter.io/google_mobile_ads)

There are ways to make it work with less code but if this way it is easier to maintain and extend your code.
You need to change how you import your files based on the platform.
First create an abstract class around Admob:
abstract class AdmobWrapper{
factory AdmobWrapper() => createAdmobWrapper();
Future<dynamic> init();
}
Second create the platform files:
Generic platform:
AdmobWrapper createAdmobWrapper() => throw UnimplementedError('createAdmobWrapper()');
IO (Mobile) platform:
AdmobWrapper createAdmobWrapper() => AdmobWrapperIO();
class AdmobWrapperIO() implements AdmobWrapper(){
factory AdmobWrapperIO() => _instance;
AdmobWrapperIO._internal();
static final AdmobWrapperIO_instance = AdmobWrapperIO._internal();
Future<dynamic> init(){
.... do init here
}
}
Web platform:
AdmobWrapper createAdmobWrapper() => AdmobWrapperWeb();
class AdmobWrapperWeb implements AdmobWrapper {
factory AdmobWrapperWeb() => _instance;
AdmobWrapperWeb._internal();
static final AdmobWrapperWeb _instance = AdmobWrapperWeb._internal();
Future<dynamic> init(){
// nothing here
}
}
Third add platform imports to your abstract class:
import 'package:genericPlatform.dart' // <-- this 2 slash is important
if(dart.library.io) 'package:yourRealAdmobPackage'
if(dart.library.html) 'package:yourWebPackage'
abstract class AdmobWrapper(){ /// rest of the code we made in first step
You can call AdmobWrapper().init() now.

Related

Common interface for two Flutter plugins

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.

How to access/inject ObjectBox database in repository in Flutter - Reso Coder DDD

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().

How to configure Flutter Flavor for different API links

I have 3 different API links for Staging, Development and Production Stages. I tried to configure the files in this way to share links
//Types of flavors we have
import 'dart:io';
//Types of flavors
enum Flavor {
STAGING,
DEVELOPMENT,
PRODUCTION,
}
class Config {
//Floavor is the way to devide application
//configurations depending on stage we work
static Flavor? appFlavor;
// api url for requests depending on the flavor
// you can use it by typing Config.api_url
static String get api_url {
switch (appFlavor) {
case Flavor.PRODUCTION:
return 'https://api.Link1';
case Flavor.DEVELOPMENT:
return 'https://api.Link2';
case Flavor.STAGING:
return 'https://api.Link3';
default:
return 'https://api.Link2';
}
}
//getting information about platform
//you can use it by typing Config.platform
static String get platform => Platform.isAndroid ? 'ANDROID' : 'IOS';
}
Then I initialise the flavor in main.dart in the following way :
void main() {
//Initialising the flavor
Config.appFlavor = Flavor.PRODUCTION;
Bloc.observer = AppBlocObserver();
FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack);
};
runZonedGuarded(
() => runApp(const IChazy()),
(error, stackTrace) => log(error.toString(), stackTrace: stackTrace),
);
}
But then when I try to pass the data to Chopper package for baseURL it shows me mistake that I should use the const value:
import 'package:chopper/chopper.dart';
import 'package:ichazy/config/flavor_config.dart';
//part 'challenges_api_service.chopper.dart';
#ChopperApi(baseUrl: Config.api_url) //Error Appears here
abstract class ChallengeApiService {}
The Error:
Const variables must be initialized with a constant value.
Try changing the initializer to be a constant expression.
I know that I should use const value in order to cancel this error but in the same time I want to switch flavors and API links. Is there any proper way to somehow manage this two goals, maybe by changing Config class?
Thank you in advance.
The only way I found is to use custom create method in the chopper package
#ChopperApi(baseUrl: '/')
abstract class ChallengeApiService extends ChopperService {
#Get()
Future<Response> getChallenges(
#Body() Map<String, dynamic> body,
);
static ChallengeApiService create() {
final client = ChopperClient(
baseUrl: Config.api_url,
services: [_$ChallengeApiService()],
converter: JsonConverter());
return _$ChallengeApiService(client);
}
}

I want to execute this method in the plugin, what should I do

I am doing a function of external physical keyboard input, scan code gun to obtain data, execute this method, and then return to flutter app from the plugin
Now I have a problem, how to override this method in the plug-in, it is currently ineffective
enter image description here
Flutter provides a way to communicate with the native platform code using MethodChannel.
From the flutter MethodChannel documentation (here):
Flutter uses a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
Note: If desired, method calls can also be sent in the reverse direction, with the platform acting as client to methods implemented in Dart. A concrete example of this is the [quick_actions][2] plugin.
MethodChannel usage to invoke a function on native platform-code from flutter:
We create a method channel object in Flutter, iOS, and Android with the same name. The Android and iOS objects will set a method call handler to receive calls from Flutter. The Flutter code can then call invokeMethod to call the handlers on the native objects
Flutter:
static final channelName = 'com.example.widget/keyEvent';
final methodChannel = MethodChannel(channelName);
await this.methodChannel.invokeMethod("dispatchKeyEvent");
Android:
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example.widget/keyEvent";
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("dispatchKeyEvent")) {
//TODO: Call the dispatch key event function here
bool resultValue;
if (data != null) {
result.success(data);
} else {
result.error("UNAVAILABLE", "Result not available", null);
}
} else {
result.notImplemented();
}
}
);
}
}

Flutter - Load assets for tests

Not sure if it's a limitation or something, but below code does not load anything. I have some data driven behaviour that I'd like to test isolated.
class Loader
import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;
class Loader {
Future<String> load() async{
return await rootBundle.loadString('assets/json/sketch.json');
}
}
The test
testWidgets('Should parse load sketch.json', (WidgetTester tester) async {
var loaderFuture = new Loader();
Future<String> resultFuture = loaderFuture.load();
resultFuture.then((value) => print(value))
.catchError((error) => print(error));
while(true){};
});
Future does not return neither success nor error and hangs forever. I know
the while(true) locking up the test, but for now I just wanted to see sketch.json printed
Asset location
To use rootBundle in your tests you need this at the beginning of your test programs:
import 'package:flutter_test/flutter_test.dart';
...
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
See the documentation of DefaultAssetBundle it describes using it and a AssetBundle to provide your own assets.
Create a class that wraps the rootBundle:
#injectable
class AssetsManager {
Future<String> loadString(String path) {
return rootBundle.loadString(path);
}
}
Then inject it to your class and in your test override its dependency:
getIt.unregister<AssetsManager>();
getIt.registerSingleton<AssetsManager>(AssetsManagerMock());
Based on your test scenario, Configure what will be returned when calling loadString by using Mocktail's when method.
I'm using get_it for DI. Hope it's clear enough.