Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to register plugins in io.flutter.embedding.android.FlutterActivity #41102

Closed
ZhenTs opened this issue Sep 23, 2019 · 52 comments
Closed

How to register plugins in io.flutter.embedding.android.FlutterActivity #41102

ZhenTs opened this issue Sep 23, 2019 · 52 comments

Comments

@ZhenTs
Copy link

@ZhenTs ZhenTs commented Sep 23, 2019

I am using Experimental:-Add-Flutter-Activity to add a Flutter Activity with my native android app, but some plugin are not work.

I found that I didn't register plugins at all, Can anyone tell me how to do it at io.flutter.embedding.android.FlutterActivity?

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.view.FlutterMain;

public abstract  class BaseFlutterActivity extends FlutterActivity {
    
@Override
    protected void onCreate(Bundle savedInstanceState) {
        FlutterMain.startInitialization(this);
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        initMethodChannel();
    }
[✓] Flutter (Channel master, v1.10.6-pre.29, on Mac OS X 10.14.6 18G95, locale zh-Hans-CN)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.38.1)
[✓] Connected device (1 available)

• No issues found!

@askarsyzdykov

This comment has been minimized.

Copy link

@askarsyzdykov askarsyzdykov commented Sep 25, 2019

@ZhenTs You should use io.flutter.app.FlutterActivity instead of io.flutter.embedding.android.FlutterActivity. For example:

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import io.flutter.app.FlutterActivity
import io.flutter.facade.Flutter
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.view.FlutterMain
import io.flutter.view.FlutterView

class MyActivity : FlutterActivity(), LifecycleOwner {

    private val lifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle = lifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        FlutterMain.startInitialization(this)
        super.onCreate(savedInstanceState)
        GeneratedPluginRegistrant.registerWith(this)
        initMethodChannel();
    }

    override fun createFlutterView(context: Context?): FlutterView {
        val flutterView = Flutter.createView(this, lifecycle, null)
        setContentView(flutterView)
        return flutterView
    }
}
@richardheap

This comment has been minimized.

Copy link

@richardheap richardheap commented Oct 8, 2019

According to this, you need to override configureFlutterEngine, for example:

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
    GeneratedPluginRegistrant.registerWith(new ShimPluginRegistry(flutterEngine));
  }
@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 23, 2019

@ZhenTs Have you solved this ?

@richardheap

This comment has been minimized.

Copy link

@richardheap richardheap commented Oct 23, 2019

Yes, by overriding configureFlutterEngine.

@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 24, 2019

@richardheap thanks. my issue is that I'm using a mix of old and new (already migrated to embedding) plugins which requires a bit more work.

@askarsyzdykov

This comment has been minimized.

Copy link

@askarsyzdykov askarsyzdykov commented Oct 24, 2019

@vitor-gyant @richardheap
is io.flutter.app.FlutterActivity obsolete?

@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 24, 2019

@askarsyzdykov Not really but the master branch already includes the new embedding api which solves some issues that I'm currently facing. If you target to stable then everything works as expected.

@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 29, 2019

@matthew-carroll I'd like ask your help to understand how FlutterView should be used in the v2 embedding.

Currently, I'm creating my CustomView which extends from FlutterView. In order to attach and detach I've implemented the following methods:

   @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        detachFromFlutterEngine();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        attachToFlutterEngine(MyFlutterEngine.getInstance(getContext()));
    }

This seems to work just fine but when I use a FlutterFragment and then a FlutterView again it seems the ui events are no longer received in the FlutterView. There is some information regarding AppLifecycleState.resumed in the new embedding wiki but I'm not able to understand how it applies in the new embedding flow.

I didn't found any information how to forward onRequestPermissionsResult into the FlutterView in the same way we do it in the Fragment scenario.

Your help would be greatly appreciated. Thanks in advance,

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Oct 29, 2019

Can you elaborate a bit on what you're looking to achieve with a custom FlutterView?

Generally speaking, if you're going to use a custom FlutterView, you'll need to replicate the same logic that currently exists in FlutterActivity and FlutterFragment, which has mostly been centralized in a delegate here:
https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 30, 2019

@matthew-carroll Sure. I've created a native Android SDK on top of flutter. The SDK exposes three ways of integration: MyChatActivity, MyChatFragment and MyChatView. Each of these classes extends from the flutter counterpart classes FlutterActivity, FlutterFragment and FlutterView.

In MyChatFragment I'd to override the following method:

@Override
    public boolean shouldAttachEngineToActivity() {
        return true;
    }

I was also forced to forward onRequestPermissionsResult to MyChatFragment otherwise Flutter was not handling request permissions correctly. I believe this is what you guys would expect.

My current problem is that when using MyChatView which extends FlutterView the permissions request is not being handled by flutter but now I'm not sure how to fix it because FlutterView does not expose onRequestPermissionsResult and it also does not have any delegate to forward the call.

So, I'm trying to understand what should be implemented on my side to fix these issues. Would FlutterActivityAndFragmentDelegate be required ? How should I apply it ?

Thanks for your time.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Oct 30, 2019

That's right, FlutterView does not receive any lifecycle or Activity calls because it's just a View. You'll have to replicate the behavior that I linked in my previous solution. If you're using a FlutterView directly, you'll need to manage the associated FlutterEngine, including sending Flutter lifecycle messages over the associated system channel. Using a FlutterView directly is not particularly easy at this point. We haven't focused on that use-case. For now you'll need to replicate all the standard Flutter behavior that exists in FlutterFragment and FlutterActivity, which is why those classes happen to exist. Does that make sense?

@vitor-gyant

This comment has been minimized.

Copy link

@vitor-gyant vitor-gyant commented Oct 31, 2019

Yes it make sense. My only remark is that on V1 version, FlutterView could be used without replicating all the logic and on v2 embedding is not the case.

FlutterActivityAndFragmentDelegate is not public which will force me to replicate all the code in a class owned by me. Would you guys consider making it public ?

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Oct 31, 2019

Yeah I think we'll investigate the standalone FlutterView use-case sometime soon, but it's not scheduled as a current priority. CC @xster for priority visibility.

@xster

This comment has been minimized.

Copy link
Contributor

@xster xster commented Nov 1, 2019

The ask is to have a compositional helper class that a non-flutter activity/fragment forwards to when using a view+engine without a flutter activity/fragment? Not currently supported but seems reasonable.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 21, 2019

What about the original issue question? Currently it looks like MethodChannel is incompatible with io.flutter.embedding.android.FlutterActivity!

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 21, 2019

@BurningAXE on master your plugins should be automatically registered in the new FlutterActivity.

If you're not on master then you need to override the following:

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
    GeneratedPluginRegistrant.registerWith(new ShimPluginRegistry(flutterEngine));
  }
@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 21, 2019

@matthew-carroll Do you have a working example for the master channel version? I wasn't able to get MethodChannel messages to and from my Flutter module.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 21, 2019

It would probably be easier if you could share an example of what's not working. Can you post the relevant code that you're attempting to use on the Android side related to plugins?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

Sure. Here is my FlutterActivity subclass:

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformViewsController
import io.flutter.plugins.GeneratedPluginRegistrant

class FlutterViewActivity : FlutterActivity() {
    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        val flutterEngine = FlutterEngineCache.getInstance().get("my_engine_id")
        return flutterEngine
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        // Create a ShimPluginRegistry and wrap the FlutterEngine with the shim.
        val shimPluginRegistry = ShimPluginRegistry(flutterEngine, PlatformViewsController())

        // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
        GeneratedPluginRegistrant.registerWith(shimPluginRegistry);
        registerFlutterCallbacks(flutterEngine)
    }

    fun registerFlutterCallbacks(flutterEngine: FlutterEngine) {
        MethodChannel(flutterEngine.dartExecutor, CHANNEL_DATA).setMethodCallHandler { call, result ->
            // manage method calls here
           ...
        }
    }
}

The engine is created and cached in my Application subclass:

    private void setupFlutter() {
        // Instantiate your FlutterEngine.
        FlutterEngine flutterEngine = new FlutterEngine(this);

        // Pre-warm your FlutterEngine by starting Dart execution.
        flutterEngine
                .getDartExecutor()
                .executeDartEntrypoint(
                        DartExecutor.DartEntrypoint.createDefault()
                );

        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);
    }

It seems that everything is working but I'm not receiving the calls on MethodChannel. Sending works, though.

@askarsyzdykov

This comment has been minimized.

Copy link

@askarsyzdykov askarsyzdykov commented Nov 22, 2019

@BurningAXE what Flutter version do you use?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

Sorry for missing that. I tried running it on v1.12.3-pre.27 which was HEAD of the master channel yesterday.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 22, 2019

@BurningAXE with the code example you posted, are you having issues with your plugins, or your standalone method channel, or both?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

@matthew-carroll I don't receive messages on the method channel from the Flutter module to the Android one.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 22, 2019

But your plugins are working as expected?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

Yes. Please separate it in another issue if you think it's unrelated to this one. I just thought it's linked.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 22, 2019

Can you also post the Dart card that you're using to try to send messages from Dart to Flutter?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

I am confused! What is a Dart card? And I'm trying to send messages from Flutter to Android module.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 22, 2019

That was a typo. I was asking for the Dart code. And Dart is the same thing as Flutter. You're trying to send a message from Dart/Flutter to the Android platform. So it might be helpful to see the Dart code where you setup the channel and try to send a message.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

Ok, got it.
In main.dart:
const dataMethodChannel = const MethodChannel('com.kanbanize.android.flutter/data');
In a widget's State:

  _BoardWidgetState() {
    dataMethodChannel.setMethodCallHandler(_receiveFromHost);
    try {
      dataMethodChannel.invokeMethod("FlutterModuleLoaded");
    } catch (e) {
      print('Failed to invoke FlutterModuleLoaded: $e');
    }
}

I don't see the log from the catch statement.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 22, 2019

and your CHANNEL_DATA constant on Android equals "com.kanbanize.android.flutter/data"?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 22, 2019

Yes:
const val CHANNEL_DATA = "com.kanbanize.android.flutter/data";

Mind you - this code was working with io.flutter.app.FlutterActivity

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 23, 2019

@BurningAXE I've setup a Kotlin add-to-app project and successfully sent a message from Dart to Android. Here are the relevant pieces of my project:

Pre-warm engine in Application subclass:

class MyApplication : Application() {
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()

        // Instantiate a FlutterEngine.
        flutterEngine = FlutterEngine(this)

        // Start executing Dart code to pre-warm the FlutterEngine.
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // Cache the FlutterEngine to be used by FlutterActivity.
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}

Subclass FlutterActivity:

class ChannelReproActivity : FlutterActivity() {
    val TAG = "ChannelReproActivity"

    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        return FlutterEngineCache.getInstance().get("my_engine_id")
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        // Add method channel.
        MethodChannel(flutterEngine.dartExecutor, "com.kanbanize.android.flutter/data").setMethodCallHandler { call, result ->
            Log.d(TAG, "Received call from Dart.")
        }
    }
}

Send message from Dart:

const dataMethodChannel = const MethodChannel('com.kanbanize.android.flutter/data');

void main() {
  runApp(MyApp());

  dataMethodChannel.setMethodCallHandler((MethodCall call) async {
    // no-op
  });
  try {
    dataMethodChannel.invokeMethod("FlutterModuleLoaded");
  } catch (e) {
    print('Failed to invoke FlutterModuleLoaded: $e');
  }
}

The above works for me.

BTW, I'm guessing that you're not really at tip of tree because the constructor that you invoke for your ShimPluginRegistry no longer exists. A PlatformViewsController is no longer sent into that constructor. So you may want to check the version of Flutter that you're building against.

Also, depending on your needs, if you register your method channel with your FlutterEngine inside your Application subclass, then you can avoid doing that in an Activity and then you can use FlutterActivity directly without subclassing. But that will only work if it's OK for you to have that channel registered as long as the FlutterEngine exists.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 25, 2019

@matthew-carroll Thanks a lot for the time you put into this!
I updated to the current tip of master (which should be v1.12.12-pre.4) and tried your code (which was mostly the same as my code). However, I hit this issue. To get past that and just give it a try I commented GeneratedPluginRegistrant.registerWith(flutterEngine)
What I found was that I do receive messages from Flutter to Android. However, the message is sent when I create the engine, not when I start the Activity! I invoke "FlutterModuleLoaded" inside the State of my main widget. It looks like Flutter starts actually building the widget tree as soon as the engine is created and "pre-warmed". Is this correct and expected?
If correct - do we still need to wait and verify Flutter is loaded or it's safe to assume it is now?

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Nov 27, 2019

@BurningAXE let's go ahead and pick this up on that other issue then.

I think this issue is resolved, so I'm going to close it. Feel free to re-open if there are unhandled issues related to this ticket.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 28, 2019

@matthew-carroll Yes, I think the issue is resolved. Can you please answer my two questions at the end as you already have the context and (hopefully) the knowledge :)

@coastalgit

This comment has been minimized.

Copy link

@coastalgit coastalgit commented Nov 29, 2019

I have managed to implement a io.flutter.embedding.android.FlutterActivity into my existing Android app OK (with a cached engine), but I am unable to get any channel comms working at all. Even a straightforward async method call from my flutter module will not trigger the MethodCallHandler in my FlutterActivity.
I could see the flutter example 'platform_channel' project is working OK with embedded FlutterActivity, but it does not have an explicit declaration for a FlutterEngine.
Just wondering if I am missing a step?

@BurningAXE - how did you resolve your original MethodCall problem?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Nov 29, 2019

@coastalgit In my case it turned out the channel messages were coming but they were coming too early! I had an automatic message to let me know Flutter has loaded (so I can send it some data). However, when you cache the engine it turns out it immediately builds the widget tree (even though it is not yet visible anywhere) and sends the message. At this time there is no FlutterActivity to receive it. I have another message that is sent at the touch of a button and I receive it in the FlutterActivity.

@coastalgit

This comment has been minimized.

Copy link

@coastalgit coastalgit commented Nov 30, 2019

@BurningAXE I see, thanks. Are you subclassing FlutterActivity? I think I must be missing something quite obvious.
@matthew-carroll Do you have the AndroidManifest extract for your ChannelReproActivity example?

@coastalgit

This comment has been minimized.

Copy link

@coastalgit coastalgit commented Dec 1, 2019

@BurningAXE @matthew-carroll @xster
I desperately need some assistance with this gents. I am unable to get a method channel open between my flutter module and the subclassed FlutterActivity in my native Android app. I do not believe I am actually getting my subclassed instance of FlutterActivity generated.

AndroidManifest.xml:

<activity android:name=".MyFlutterActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
            android:exported="true"
            android:hardwareAccelerated="true"
            android:theme="@style/AppTheme.NoActionBar"
            android:windowSoftInputMode="adjustResize"/>

<!--
<activity android:name="io.flutter.embedding.android.FlutterActivity"/>
 -->

From existing Android app:

public class MainApp extends FlutterApplication {
    FlutterEngine fe;
    @Override
    public void onCreate() {
        super.onCreate();
        fe = new FlutterEngine(this);
        fe
          .getDartExecutor()
          .executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
          );
        FlutterEngineCache.getInstance().put("my_engine_id", fe);
    }

Launching my subclass of FlutterActivity from within a native existing Android activity in the existing app:

private void loadMySubclassedFlutterActivity(){
        Intent flutterIntent = MyFlutterActivity
                .withCachedEngine("my_engine_id")
                .build(this);
        startActivity(flutterIntent);
}

My subclass:

public class MyFlutterActivity extends FlutterActivity {
    private static final String TAG = MyFlutterActivity.class.getSimpleName();
    private static final String REQUEST_CHANNEL = "add2app.io/request";

    @Override
    public FlutterEngine provideFlutterEngine(Context context) {
        Log.d(TAG, "provideFlutterEngine: ");
        return FlutterEngineCache.getInstance().get("my_engine_id");
    }

//    @Override
//    protected void onCreate(Bundle savedInstanceState) {
//        Log.d(TAG, "onCreate: ");
//        super.onCreate(savedInstanceState);
//    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        Log.d(TAG, "configureFlutterEngine: ");
        //super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        new MethodChannel(flutterEngine.getDartExecutor(), REQUEST_CHANNEL).setMethodCallHandler(
                (call, result) -> {
                    if (call.method.equals("getNativeMsg")) {
                        String nd = "Ola from Android";
                        result.success(nd);
                    } else {
                        result.notImplemented();
                    }
                }
        );
    }
}

With above code, if I leave the manifest entry commented out for FlutterActivity, I get

android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.fluttand/io.flutter.embedding.android.FlutterActivity};

If I uncomment it, my flutter app appears OK, but (i) I do not see any MyFlutterActivity log messages and (ii) the method call from within my dart code to invokeMethod('getNativeMsg') generates the exception:

MissingPluginException(No implementation found for method getNativeMsg on channel add2app.io/request)

I've seen some references to the following manifest entry, but not sure if it is relevant:

<meta-data
  android:name="flutterEmbedding"
  android:value="2" />

Building off master (29 November 2019 at 14:17:52 WET)

Any input greatly appreciated.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Dec 2, 2019

Hi @coastalgit ,
Here is my relevant code:
AndroidManifest.xml:

        <activity
            android:name=".FlutterViewActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            />

Application subclass:

import androidx.multidex.MultiDexApplication;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;

public class MyApplication extends MultiDexApplication {

	@Override
	public void onCreate() {
	    super.onCreate();
            //...
            setupFlutter();
	}

    private void setupFlutter() {
        // Instantiate your FlutterEngine.
        FlutterEngine flutterEngine = new FlutterEngine(this);

        // Pre-warm your FlutterEngine by starting Dart execution.
        flutterEngine
                .getDartExecutor()
                .executeDartEntrypoint(
                        DartExecutor.DartEntrypoint.createDefault()
                );

        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);
    }
}

FlutterActivity subclass (in Kotlin):

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class FlutterViewActivity : FlutterActivity() {

    companion object {
        const val CHANNEL_DATA = "com.kanbanize.android.flutter/data";
        const val CHANNEL_ACTIONS = "com.kanbanize.android.flutter/actions";
        val TAG = this::class.java.simpleName
        val boardKey = "boardKey"
        val workflowIdKey = "workflowIdKey"

        @JvmStatic
        fun startActivity(context: Context, board: Board, workflowId: Long) {
            val intent = Intent(context, FlutterViewActivity::class.java)
            intent.putExtra(boardKey, board)
            intent.putExtra(workflowIdKey, workflowId)
            context.startActivity(intent)
        }
    }

    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        return FlutterEngineCache.getInstance().get("my_engine_id")
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        registerFlutterCallbacks(flutterEngine.dartExecutor)
    }

    fun registerFlutterCallbacks(messenger: BinaryMessenger) {
        MethodChannel(messenger, CHANNEL_DATA).setMethodCallHandler { call, result ->
            // manage method calls here
            if (call.method == "FlutterModuleLoaded") {
                flutterModuleIsLoaded = true
            }
            else {
                result.notImplemented()
            }
        }

        MethodChannel(messenger, CHANNEL_ACTIONS).setMethodCallHandler { call, result ->
            // manage method calls here
            try {
                if (call.method == "AddNewTask") {
                    addNewTask()
                }
                else {
                    result.notImplemented()
                }
            } catch (ex: Exception) {
                Crashlytics.log(0, TAG, "Failed to handle call: " + call.toString())
            }
        }
    }
}

The AddNewTask message is sent at the tap of a button and I get the call in Android so it works. I just tested it with the latest Flutter version on the master channel - v1.12.16-pre.35
Let me know if I can help in any other way - I know how frustrating this lack of stability and helpful resources is!

@coastalgit

This comment has been minimized.

Copy link

@coastalgit coastalgit commented Dec 2, 2019

@BurningAXE - Firstly, many thanks for the rapid response.

Immediately, when I seen your startActivity helper method, I could see the cause of my problem. I was launching my subclassed Activity based on the content in the docs, whereby I was using:

Intent flutterIntent = MyFlutterActivity
                .withCachedEngine("my_engine_id")
                .build(this);
startActivity(flutterIntent);

I switched from this to the traditional means of launching an Activity:

Intent flutterIntent = new Intent(this,MyFlutterActivity.class);
startActivity(flutterIntent);

... and my subclassed Activity started ok and my existing method channel code worked :)

I tip my hat to you sir.

Out of interest, as the Add2App adventure is in its infancy -- it would be good to share/track progress. Are you on the fluttercommunity.slack.com channel?

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Dec 2, 2019

Nice! I'm really glad I could help!
As for the Slack community - hadn't thought to join but just did it out of curiosity. I am Milen Marinov there.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Dec 3, 2019

@BurningAXE

It looks like Flutter starts actually building the widget tree as soon as the engine is created and "pre-warmed". Is this correct and expected?

That's correct. When you start executing Dart code, which is the same thing as "pre-warming the engine", your main() method runs, which probably invokes runApp(), which builds your widget tree.

If correct - do we still need to wait and verify Flutter is loaded or it's safe to assume it is now?

I would not make any assumptions about timing. The safest bet is to maintain a messaging that ensures both sides of a channel/plugin are ready to handle work.

@BurningAXE

This comment has been minimized.

Copy link

@BurningAXE BurningAXE commented Dec 4, 2019

@matthew-carroll Thanks for the answers!

@Zazo032

This comment has been minimized.

Copy link

@Zazo032 Zazo032 commented Dec 16, 2019

@matthew-carroll after migrating our app to V2 embedding, this plugin stopped working, do you know what can we do to make it work again? This is a release blocker for our app avioli/uni_links#29

@arifcebe

This comment has been minimized.

Copy link

@arifcebe arifcebe commented Dec 18, 2019

@BurningAXE I've setup a Kotlin add-to-app project and successfully sent a message from Dart to Android. Here are the relevant pieces of my project:

Pre-warm engine in Application subclass:

class MyApplication : Application() {
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()

        // Instantiate a FlutterEngine.
        flutterEngine = FlutterEngine(this)

        // Start executing Dart code to pre-warm the FlutterEngine.
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // Cache the FlutterEngine to be used by FlutterActivity.
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}

Subclass FlutterActivity:

class ChannelReproActivity : FlutterActivity() {
    val TAG = "ChannelReproActivity"

    override fun provideFlutterEngine(context: Context): FlutterEngine? {
        return FlutterEngineCache.getInstance().get("my_engine_id")
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec.
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        // Add method channel.
        MethodChannel(flutterEngine.dartExecutor, "com.kanbanize.android.flutter/data").setMethodCallHandler { call, result ->
            Log.d(TAG, "Received call from Dart.")
        }
    }
}

Send message from Dart:

const dataMethodChannel = const MethodChannel('com.kanbanize.android.flutter/data');

void main() {
  runApp(MyApp());

  dataMethodChannel.setMethodCallHandler((MethodCall call) async {
    // no-op
  });
  try {
    dataMethodChannel.invokeMethod("FlutterModuleLoaded");
  } catch (e) {
    print('Failed to invoke FlutterModuleLoaded: $e');
  }
}

The above works for me.

BTW, I'm guessing that you're not really at tip of tree because the constructor that you invoke for your ShimPluginRegistry no longer exists. A PlatformViewsController is no longer sent into that constructor. So you may want to check the version of Flutter that you're building against.

Also, depending on your needs, if you register your method channel with your FlutterEngine inside your Application subclass, then you can avoid doing that in an Activity and then you can use FlutterActivity directly without subclassing. But that will only work if it's OK for you to have that channel registered as long as the FlutterEngine exists.

Hi @matthew-carroll , When i use this this example, how can i set route or custom route in flutter. In Flutter version 1.7.x i use this method to set route in flutter.

override fun createFlutterView(context: Context?): FlutterView {
        val layoutParams = WindowManager.LayoutParams(-1, -1)
        val nativeView = this.createFlutterNativeView()
        val flutterLayoutView = FlutterView(this, null, nativeView)
        flutterLayoutView.layoutParams = layoutParams
        flutterLayoutView.setInitialRoute(routeValue)
        this.setContentView(flutterLayoutView)
        return flutterLayoutView
    }
@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Dec 20, 2019

@Zazo032 which plugin stopped working? Can you please include the commit version of the plugin that you're using and your output from flutter doctor? Also, can you describe what's not working? Do you have a stack trace of an error?

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Dec 20, 2019

@arifcebe you can set the initial route by adjusting my earlier example to look like the following:

class MyApplication : Application() {
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()

        // Instantiate a FlutterEngine.
        flutterEngine = FlutterEngine(this)

        // Set an initial route.
        flutterEngine.navigationChannel.initialRoute = "your_route_here";

        // Start executing Dart code to pre-warm the FlutterEngine.
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // Cache the FlutterEngine to be used by FlutterActivity.
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}
@Zazo032

This comment has been minimized.

Copy link

@Zazo032 Zazo032 commented Dec 23, 2019

@Zazo032 which plugin stopped working? Can you please include the commit version of the plugin that you're using and your output from flutter doctor? Also, can you describe what's not working? Do you have a stack trace of an error?

https://pub.dev/packages/uni_links

I know it's a third party plugin, and I would happily work a PR to fix it, but I'm not really sure on how to do it.

This is the console output:

I/flutter (18156): MissingPluginException(No implementation found for method getInitialLink on channel uni_links/messages)

And as that method doesn't seem to have a valid implementation with v2 embedding, we can't open external links inside our app

flutter doctor

[√] Flutter (Channel master, v1.13.6-pre.16, on Microsoft Windows [Versión 10.0.18363.535], locale es-ES)
    • Flutter version 1.13.6-pre.16 at C:\flutter
    • Framework revision fcaf9c4070 (2 days ago), 2019-12-21 14:03:01 -0800
    • Engine revision 33813929e3
    • Dart version 2.8.0 (build 2.8.0-dev.0.0 886615d0f9)

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at C:\Users\crist\AppData\Local\Android\sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-29, build-tools 29.0.2
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
    • All Android licenses accepted.

[√] Android Studio (version 3.5)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin version 42.1.1
    • Dart plugin version 191.8593
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
@huage2580

This comment has been minimized.

Copy link

@huage2580 huage2580 commented Jan 8, 2020

that has a simple (but not recommended)way to do this.

startActivity(
                    FlutterActivity
                            .withNewEngine()
                            .initialRoute("/page2")
                            .build(this)
                            .setClass(this,FlutterHostActivity.class)
            );

then override configureFlutterEngine method.

@matthew-carroll

This comment has been minimized.

Copy link
Contributor

@matthew-carroll matthew-carroll commented Jan 13, 2020

@Zazo032 would you mind filing a new bug for that plugin specifically and the error you're seeing? If you tag me in it then I'll add it to my list and see what I can find out about that particular plugin.

@Zazo032

This comment has been minimized.

Copy link

@Zazo032 Zazo032 commented Jan 14, 2020

Seems almost fixed with this PR: avioli/uni_links#55
Will wait until it lands, as far as we have tested, the rest of the plugins work properly. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.