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
}
}
Related
I want to send data with intent from native android app to flutter app, So if flutter app is closed below code working fine to fetch intent data in main.dart file. If flutter app is in foreground and i tries to send data from native app to flutter app nothing happens. Is their anything else to need implement for this case?
Native app code to start flutter app
var intent = getPackageManager().getLaunchIntentForPackage("com.flutterapp");
intent.putString( "MapParams", jsonObj.toString())
startActivity(intent)
Flutter app code
class MainActivity: FlutterActivity()
var sharedData="";
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
"share_channel").setMethodCallHandler { call, result ->
if (call.method == "MapParams") {
handleIntent()
result.success(sharedData)
sharedData = ""
}
}
}
private fun handleIntent() {
if (getIntent().hasExtra("MapParams")) {
getIntent().getSerializableExtra("MapParams")?.let { intentData ->
sharedData = intentData.toString()
}
}
}
Main.Dart file
Future<String> getSharedData() async {
return await MethodChannel('share_channel')
.invokeMethod("MapParams") ??
"";
}
I am trying to create a Android app which launches flutter inside it. I have learnt that we can pass data to flutter via MethodChannels like this:
MethodChannel(
FlutterEngineCache.getInstance().get(NEARBY_PLACES_ENGINE_KEY)?.dartExecutor?.binaryMessenger,
"APP_CHANNEL"
).setMethodCallHandler { call, result ->
if (call.method == "getGreetings") {
val coordinates = "Hello"
result.success(coordinates)
}
}
startActivity(FlutterActivity
.withCachedEngine(NEARBY_PLACES_ENGINE_KEY)
.build(this))
This is possible when I have access to FlutterEngine instance (In this case with help of FlutterEngineCache). But how do we get the BinaryMessenger of a engine created with FlutterActivity.withNewEngine() ? Please help. TIA!
Okay so I found the way to do it.
Created a custom FlutterActivity class and added that in the manifest instead of the one from io.flutter.embedding.android.FlutterActivity. The custom FlutterActivity extends from FlutterActivity, and overrides some methods to be able to access the newEngine.
class CustomFlutterActivity : FlutterActivity() {
companion object {
var methodChannelInvoker: (MethodCall, Result) -> Unit = { _, _ -> }
fun withCachedEngine(cachedEngineId: String): CachedEngineIntentBuilder {
return CachedEngineIntentBuilder(CompassFlutterActivity::class.java, cachedEngineId)
}
fun withNewEngine(): NewEngineIntentBuilder {
return NewEngineIntentBuilder(CompassFlutterActivity::class.java)
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "APP_CHANNEL")
.setMethodCallHandler { call, result ->
methodChannelInvoker(call, result)
}
}
}
and at the launch site,
CustomFlutterActivity.methodChannelInvoker = { call, result ->
if (call.method == "getGreetings") {
val greetings = "Hello there!"
result.success(coordinates)
}
}
startActivity(CustomFlutterActivity
.withNewEngine()
.initialRoute("/custom_route")
.build(this))
This way, when the new engine launches, you get callback at configureFlutterEngine with new engine as parameter, we can simply access dartExecutor at that point.
Oh yes, and don't forget to add the CustomFlutterActivity in the manifest instead of FlutterActivity.
I'm running UI testing on Android devices using Appium. We recently migrated to JUnit5 and I'm attempting to utilize the #BeforeAll class to make sure the app is in a good state before we continue to the next class.
Currently, the tooltip in Android studio is indicating that the function is never used. In the log I'm seeing a junitException saying that the method must be static. I haven't implemented #TestInstance yet, I'd like to be able to use beforeAll without it for now. I'm just confused why it isn't working since my #beforeEach and #afterEach are both working. The error and code are below.
org.junit.platform.commons.JUnitException: #BeforeAll method 'public final void com.bypass.automation.BaseTest.healthcheck()' must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS).
open class BaseTest {
lateinit var driver: AndroidDriver<MobileElement>
private val capabilities = DesiredCapabilities().apply {
setCapability(APPIUM_VERSION, "1.19.1")
setCapability(PLATFORM_NAME, "Android")
setCapability(DEVICE_NAME, "Android")
setCapability("appPackage", "com.ourpackage")
setCapability("appActivity", "com.ourpackage.PassthroughHomeActivity")
setCapability("automationName", "uiautomator2")
setCapability("skipDeviceInitialization", true)
setCapability("noReset", true)
setCapability("full-reset", false)
setCapability("enableMultiWindows", false)
setCapability("unlockType", "pin")
setCapability("unlockKey", "0000")
setCapability("newCommandTimeout", "120")
}
#BeforeAll
fun healthcheck() {
val currentActivity = driver.currentActivity()
println("Current activity is $currentActivity")
if (currentActivity.contains("StationSecurePayActivity")) {
println("Exiting Station Pay")
CreditCardEntryView(driver).clickBackButton()
}
when {
currentActivity.contains("kiosk") -> {
Thread.sleep(2000)
println("Exiting Kiosk")
KioskView(driver).exitKiosk()
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity != ".LoginActivity" -> {
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity.contains(".LoginActivity") -> {
println("Session was properly logged out. No action taken.")
}
}
}
#BeforeEach
fun setup() {
driver = AndroidDriver(URL("http://127.0.0.1:4750/wd/hub"), capabilities)
driver.manage()?.timeouts()?.implicitlyWait(30, SECONDS)
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
}
}
#AfterEach
fun teardown() {
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
driver.quit()
}
else {
driver.quit()
}
}
}
It will work. I believe that any method annotated with #BeforeAll must be static (unless the "per-class" test instance lifecycle is used). So it sounds to me like you should switch to that by adding this annotation to your test class: #TestInstance(Lifecycle.PER_CLASS)
Also, it is usual practice to make your setup and teardown methods public. Also, I recommend use of Selenium-Jupiter framework (https://github.com/bonigarcia/selenium-jupiter/blob/master/README.md#appium) . Good luck.
If you want to have an initialization block you may put it simply into
init{} method. And you don't have to annotate it.
So I am making a Flutter plugin and I am attempting to run Kotlin code on Android. The problem is, this code runs a method which attempts to start an activity without the FLAG_ACTIVITY_NEW_TASK flag on the intent. The problem with this is that it also does NOT have a way to give it an intent instance as it attempts to instantiate an instance inside the method itself. The method expects to be called from a button or other method that is stored on the UI and called from it. However, since it is called from the onMethodCall method in the Flutter plugin, it does not seem to work. I have attempted many workarounds such as adding a method inside the Activity and running the code inside while calling it from the flutter plugin class. I have also tried using the UIThread and no luck either. Any workarounds?
Note: I have not provided any code due to keeping this API hidden. It should only be known that I am running the code from the onMethodCall event.
Error: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
You can extend your plugin to implement ActivityAware in your plugin class, when you implement it, you get a couple of callbacks that gives you the current activity. Like this :
lateinit activity: Activity? = null
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
After that you can just startActivity from the assigned activity variable.
Let me know if you need further help.
As you mentioned, For Flutter plugin any platform-dependent logics should be kept in the subclass of FlutterActivity which was used to show flutter module/screens inside a native module. Now you can launch intent from that subclass without any additional flags.
#note - Subclass of FlutterActvity should be kept in the native module.
class FlutterResponseActivity : FlutterActivity() {
private var methodResult: Result? = null
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return MyApplication.mContext.flutterEngine //Pre-warmed flutter engine
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"startMainActivity" -> {
startMainActivity()
result.success(true)
}
else -> result.notImplemented()
}
}
}
private fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
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