I have successfully integrated flutter module in my native android application by following steps here .
The process of caching flutter engine I have already done in Application class. I am launching my flutter screen with this from android fragment.
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(currentActivity)
);
Now I want to pass my auth token to flutter module for making api calls.
I am following the process from here and created method channel in dart code but I do not know where to create the method channel in the native side.
If I am creating it in project/moduleName/.android/app/src/main/java/com/package/host/MainActivity.java
It is giving exception Unhandled Exception: MissingPluginException(No implementation found for method
Also note that this folder is placed in .gitignore by default when I created this flutter module in Android studio.
I already has a look at older tutiorals but this caching of flutter engine option is not there in them.
Please tell where am I doing wrong ?
You need to have a reference for the Flutter Engine and then use it to create the Method Channel. Take in account that the activity/fragment that launch the Flutter activity is in charge to manage the Method Channel.
private const val FLUTTER_ENGINE_ID = "flutter_engine"
private const val CHANNEL = "myApp.flutter.dev/auth"
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private lateinit var flutterEngine: FlutterEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupFlutterEngine()
setupMethodChannel()
setSupportActionBar(findViewById(R.id.toolbar))
fab.setOnClickListener {
launchFlutterModule()
}
}
private fun setupFlutterEngine() {
createAndConfigureFlutterEngine()
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine)
}
private fun createAndConfigureFlutterEngine() {
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
private fun setupMethodChannel() {
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
// All your implementation for auth token
}
}
private fun launchFlutterModule() {
startActivity(getFlutterIntent())
}
private fun getFlutterIntent(): Intent {
return FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
.build(this)
}
}
You can make a Wrapper where you can hold the method channel code and flutter engine setup, and this wrapper is init in your Application class or maybe init and injected with Dagger or Hilt where is needed.
You can pass simple data as request parameters.
Android code:
FlutterActivity
.withNewEngine()
.initialRoute("/MyPage?username=my_user&age=30&gender=male")
.build(MainActivity.this)
Flutter code, I use GetX library to get the parameters
Map<String, String?> parameters = Get.parameters;
print("parameters: $parameters");
Output:
parameters: {username: my_user, age: 30, gender: male}
First create flutter platform client with channel
set methodCallHamdler passing same channel
Refer this official doc-
https://flutter.dev/docs/development/platform-integration/platform-channels
Related
I am trying to integrate stripe terminal code with my android app build using kotlin, unfortunately I am getting the following run time error which I could not able to fix
java.lang.IllegalStateException: initTerminal must be called before attempting to get the instance
The code I have added is used below
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pay_screen)
onDiscoverReaders()
}
fun onDiscoverReaders() {
val config = DiscoveryConfiguration(
timeout = 0,
discoveryMethod = DiscoveryMethod.LOCAL_MOBILE,
isSimulated = false,
location = "xxxxxxxxxxxxxxx"
)
// Save this cancelable to an instance variable
discoveryCancelable = Terminal.getInstance().discoverReaders(config,
discoveryListener = object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
}
}
, object : Callback {
override fun onSuccess() {
println("Finished discovering readers")
}
override fun onFailure(e: TerminalException) {
e.printStackTrace()
}
})
}
I have added this to one of my activity and my intention is to check if my phone is supporting stripe tap on mobile
I guess the issue could be calling onDiscoverReaders() from a wrong place, someone please help me to fix this issue
Thanks in advance
In stripe docs you can check
// Create your listener object. Override any methods that you want to be notified about
val listener = object : TerminalListener {
}
// Choose the level of messages that should be logged to your console
val logLevel = LogLevel.VERBOSE
// Create your token provider.
val tokenProvider = TokenProvider()
// Pass in the current application context, your desired logging level, your token provider, and the listener you created
if (!Terminal.isInitialized()) {
Terminal.initTerminal(applicationContext, logLevel, tokenProvider, listener)
}
// Since the Terminal is a singleton, you can call getInstance whenever you need it
Terminal.getInstance()
might be you missed to initialise terminal before getting Instance so try add above code before onDiscoverReaders()
The error speaks for itself - first you need to initialize the api terminal, and then call the terminal instance.
Based on the documentation, we follow the following steps to get started with the api terminal:
Initialize the terminal application in the application class of the
application
class App : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
We request the necessary permissions for correct work with the
terminal search (bluetooth, geolocation), if everything is provided,
we call the init terminal with parameters like that:
Terminal.initTerminal(
context = context,
logLevel = LogLevel.VERBOSE,
tokenProvider = TokenProvider(),
listener = object : TerminalListener {
override fun onUnexpectedReaderDisconnect(reader: Reader) {
Log.d("log", "onUnexpectedReaderDisconnect")
}
override fun onConnectionStatusChange(status: ConnectionStatus) {
super.onConnectionStatusChange(status)
Log.d("log", "onConnectionStatusChange")
}
override fun onPaymentStatusChange(status: PaymentStatus) {
super.onPaymentStatusChange(status)
Log.d("log", "onPaymentStatusChange")
}
}
)
After this initialization, you can call the terminal instance and
work with it.
I am trying to open FlutterActivity in my existing android application. Before I was creating new flutter engine every time I was opening activity like this:
FlutterActivity
.withNewEngine()
.build(context)
And everything was working fine besides a little lag while opening the activity. To get rid of the lag I wanted to switch to using cached engine. I followed this official tutorial: LINK
And ended up with smething like this:
In my Application class:
class App : Application() {
lateinit var flutterEngine: FlutterEngine
override fun onCreate() {
...
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
.getInstance()
.put("myEngineId", flutterEngine)
}
}
And later in my application on the button click, in the same place that I was successfully opening FlutterActivity:
FlutterActivity
.withCachedEngine("myEngineId")
.build(context)
So I basically followed the all the instructions but the effect that I get now is after the button click there is even longer lag than before and then there is only black screen being displayed. My flutter screen is not displayed and application is kind of frozen I can't go back or do anything. There is also no error or any useful info in the logs. I have no idea what is going on. What am I doing wrong?
To Use cached FlutterEngine
In FlutterActivity you must declare provideFlutterEngine method.
class DemoActivity : FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? =
FlutterEngineCache.getInstance().get(FlutterConstants.ENGINE_ID)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo-channel")
.setMethodCallHandler { call, result ->
if (call.method == "demo-method") {
demoMethod()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun demoMethod() {
// Do native code
}
}
I'm trying to migrate from version 3.0.0 that used conductor-rxlifecycle to version 3.1.4 that is using conductor-archlifecycle and conductor-autodispose.
my current code has extension functions that binds to the lifecycle - and I'm trying to understand what is the code change needed to adjust it to archlifecycle and auto-dispose.
I would appreciate some help here - couldn't figure it out from the demo code.
conductor-archlifecycle demo
conductor-autodispose demo
protected fun <C : RxController> Completable.bindToController(controller: C): Completable =
observeOn(AndroidSchedulers.mainThread()).compose(controller.bindToLifecycle<Any>())
protected fun <C : RxController> Completable.bindUntil(controller: C, event: ControllerEvent): Completable =
observeOn(AndroidSchedulers.mainThread()).compose(controller.bindUntilEvent<Any>(event))
I assume that the controller type should be LifecycleController instead of RxController, but I don't understand what is the replacement of bindToLifecycle
I opened this issue , but I'm trying to get some help here as well
This is the change I did to my code to match the new Conductor version:
The 2 functions above were replaced by this function:
fun Completable.autoDisposable(event: ControllerEvent? = null): CompletableSubscribeProxy =
observeOn(AndroidSchedulers.mainThread())
.autoDisposable(getScopeProvider(event))
Note that the return type is now CompletableSubscribeProxy and not Completable so the location of the call in the chain might need to be changed.
I create different scopes:
private val scopeProvider: ControllerScopeProvider by lazy { ControllerScopeProvider.from(this) }
private val destroyScopeProvider: ControllerScopeProvider by lazy {
ControllerScopeProvider.from(
this,
ControllerEvent.DESTROY
)
}
...
And this is how getScopeProvider looks
private fun getScopeProvider(event: ControllerEvent?): ControllerScopeProvider =
when (event) {
null -> scopeProvider
ControllerEvent.DETACH -> detachScopeProvider
ControllerEvent.DESTROY_VIEW -> destroyViewScopeProvider
ControllerEvent.DESTROY -> destroyScopeProvider
else -> throw RuntimeException("Scope for event ${event.name} wasn't created")
}
I have created a custom MethodChannel "com.example.app/widget" that updates a home screen widget on Android after receiving a Firebase Cloud Message. It runs fine when it is called while the app is in the foreground, but I would also like to call it when a Firebase Cloud Message is received while the app is closed or in the background.
When the app is in the background, it gives me a MissingPluginException error, like below:
E/flutter (28540): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: MissingPluginException(No implementation found for method updateFromFlutter on channel com.example.app/widget)
E/flutter (28540): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:157:7)
... and so on. There are a lot of other threads about MissingPluginException errors that deal with adding a plugin to the registry, but I haven't been able to find any that address custom MethodChannels that are not part of another plugin. Is it possible to add my custom MethodChannel to the registry or do something similar that will result in the Dart code being able to call a method from that channel while in the background?
I have tried using workmanager and android_alarm_manager and they seem to run fine themselves, but they still can't get past this block with my custom channel.
My MainActivity.kt has the method details in it:
class MainActivity: FlutterActivity(), MethodChannel.MethodCallHandler {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/widget")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"updateFromFlutter" -> {
val views = RemoteViews(context.packageName, R.layout.appwidget).apply {
setTextViewText(R.id.text, call.argument("text"))
}
val manager = AppWidgetManager.getInstance(this)
manager.updateAppWidget(call.argument("idNumber"), views)
}
}
}
}
Then in main.dart I call:
Future<void> updateAndroidWidget(String text) async {
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel platform = MethodChannel('com.example.app/widget');
try {
platform.invokeMethod("updateFromFlutter", {
"text": text,
"idNumber": savedPreferences.androidWidgetID
});
} catch (e) {
print("failed: $e");
}
}
I already have Flutter Android Embedding V2 (Flutter Version >= 1.12).
Any help is greatly appreciated.
How can an EMM/MDM controlled device's flutter built app read the mananged application config profile information?
Background. The application currently is reading environment variables that are getting pushed in via the build process and I am looking to change this to using an MDM application profile that we can control.
Today:
const environment = String.fromEnvironment('environment', defaultValue: 'dev');
Where I want to go is implement this in flutter but I can't determine how to get access to:RestrictionsManager:
var myRestrictionsMgr =
activity?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
I was able to figure this out. In the MainActivity.kt:
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.yourstuff.whatever/something"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
var myRestrictionsMgr =
activity?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
var appRestrictions: Bundle = myRestrictionsMgr.applicationRestrictions
result.success(appRestrictions.getString(call.method))
}
}
}
And in flutter to call it:
const platform = const MethodChannel("com.yourstuff.whatever/something");
platform.invokeMethod("restrictionname");