In the previous article we introduced how to incorporate the Flutter module into iOS projects. In this article we will introduce how to incorporate the Flutter module into Android projects.

It is recommended to read the Flutter Hybrid Development – iOS article first. Some of the same things mentioned in this article will not be covered in the iOS article.

Now enter the implementation of Flutter and Android hybrid development.

Build an Android project

First we set up an Android project with the home page of “Bottom Navigation Activity”. Then we changed the code to rename the three fragments as HomeFragment, ChannelFragment and MineFragment.

fragment_home.xml
<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#2196F3" Tools :context=".ui.home.HomeFragment"> // This FrameLayout will be used to display the contents of the Flutter Module <FrameLayout android:id="@+id/main_fl" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="56dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code

After the construction is completed, the effect of App is as follows:

Our next job is to add the MainApp(),ChannelApp() and MineApp() of Flutter to the three fragments mentioned above.

Android project introduces the Flutter Module

  • First make sure Android Studio has the Flutter Plugin installed (the process of installing the Flutter plugin is omitted)

  • Use File > New > Import Module… -> Select the flutter Module and specify a module name

  • Fill in the corresponding information

There is a warning in the picture, because I did not take a screenshot in the first operation, and a warning for the second repeated operation.

  • Click OK and waitGradle synccomplete

Android project integrates the Flutter Module

To create aFlutterEngineGroupobject

FlutterEngineGroup can be used to manage multiple FlutterEngine objects, and resources can be shared among multiple FlutterEngines. Thus, establishing multiple FlutterEngines takes relatively less resources.

FBApplication.kt
class FBApplication: Application() { lateinit var engineGroup: FlutterEngineGroup Override fun onCreate() {super.oncreate () // Create the FlutterEngineGroup object engineGroup = FlutterEngineGroup(this) } }Copy the code
AndroidManifest.xml
<application android:name=".fbApplication" > <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>Copy the code

To create aFBFlutterEngineManagerCache Management class

We create a static method flutterEngine in FBFlutterEngineManager:

object FBFlutterEngineManager { fun flutterEngine(context: Context, engineId: String, entryPoint: String): FlutterEngine { // 1. From the cache access FlutterEngine var engine. = FlutterEngineCache getInstance () get (engineId) if (engine = = null) {/ / If there is no FlutterEngine in the cache // 1. The newly built FlutterEngine, Perform the entrance was entryPoint function of val app = context. The applicationContext as FBApplication val dartEntrypoint = DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint) engine = app.engineGroup.createAndRunEngine(context, dartEntrypoint) // 2. Deposited in the cache FlutterEngineCache. GetInstance (). The put (engineId, engine)} return engine!! }}Copy the code

Here we use the FlutterEngineCache cache class. We first get the cached FlutterEngine from it. If not, we create a new FlutterEngine and cache it.

Fragment embedded Flutter Module

This step is to bind FlutterEngine and FlutterFragment and display.

Class HomeFragment: Fragment() {// 1. FlutterEngine override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { return inflater.inflate(R.layout.fragment_home, container, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 2. Get FlutterEngine object through FBFlutterEngineManager engine = FBFlutterEngineManager. FlutterEngine (requireActivity (), R.id.main_fl.toString(), "main") // 3. Construct a FlutterFragment with FlutterEngine object val FlutterFragment = FlutterFragment.withCachedEngine(R.id.main_fl.toString()).build<FlutterFragment>() // 4. Display FlutterFragment parentFragmentManager. BeginTransaction (). The replace (R.i d.m ain_fl, FlutterFragment). The commit ()}}Copy the code

We use the cached FlutterEngine to save resources because the Fragment of the Bottom Navigation Activity will be re-created and destroyed when it switches back and forth.

Here’s how it looks:

Write plug-in code

Similar to iOS, we need to hide the BottomNavigationView in activity_main. XML when entering the secondary page and display it when returning to the primary page.

  • Add show and hide methods
class MainActivity : AppCompatActivity() {

    fun switchBottomView(show: Boolean) {
        val navView: BottomNavigationView = findViewById(R.id.nav_view)
        if (show) {
            navView.visibility = View.VISIBLE
        } else {
            navView.visibility = View.GONE
        }
    }

}
Copy the code
  • MethodChannel registers a callback
var channel: MethodChannel = MethodChannel(engine.dartExecutor.binaryMessenger, "fbmovie.com/tab_switch")
channel.setMethodCallHandler { call, result ->
    when (call.method) {
        "showTab" -> {
            val activity = requireActivity() as MainActivity
            activity.switchBottomView(true)
            result.success(null)
    }
    "hideTab" -> {
        val activity = requireActivity() as MainActivity
        activity.switchBottomView(false)
        result.success(null)
    }
    else -> {
        result.notImplemented()
    }
}
}
Copy the code

code

FBApplication.kt

class FBApplication: Application() {

    lateinit var engineGroup: FlutterEngineGroup

    override fun onCreate() {
        super.onCreate()
        engineGroup = FlutterEngineGroup(this)
    }

}
Copy the code
FBFlutterEngineManager.kt
object FBFlutterEngineManager { fun flutterEngine(context: Context, engineId: String, entryPoint: String): FlutterEngine {var engine = FlutterEngineCache. GetInstance () get (engineId) if (engine = = null) {/ / if it is empty, create, Then save val app = context. ApplicationContext as FBApplication val dartEntrypoint = DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint) engine = app.engineGroup.createAndRunEngine(context, dartEntrypoint) FlutterEngineCache.getInstance().put(engineId, engine) } return engine!! }}Copy the code
MainActivity.kt
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {/ / modify status bar color if (Build) VERSION) SDK_INT > = Build. VERSION_CODES. M) {window. DecorView. SystemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR } super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navView: BottomNavigationView = findViewById(R.id.nav_view) val navController = findNavController(R.id.nav_host_fragment) navView.setupWithNavController(navController) } fun switchBottomView(show: Boolean) { val navView: BottomNavigationView = findViewById(R.id.nav_view) if (show) { navView.visibility = View.VISIBLE } else { navView.visibility = View.GONE } } }Copy the code
HomeFragment.kt
class HomeFragment : Fragment() { private lateinit var engine: FlutterEngine private lateinit var channel: MethodChannel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { return inflater.inflate(R.layout.fragment_home, container, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) engine = FBFlutterEngineManager.flutterEngine(requireActivity(), R.id.main_fl.toString(), "main") val flutterFragment = FlutterFragment.withCachedEngine(R.id.main_fl.toString()).build<FlutterFragment>() parentFragmentManager.beginTransaction().replace(R.id.main_fl, flutterFragment).commit() channel = MethodChannel(engine.dartExecutor.binaryMessenger, "fbmovie.com/tab_switch") channel.setMethodCallHandler { call, result -> when (call.method) { "showTab" -> { val activity = requireActivity() as MainActivity activity.switchBottomView(true) result.success(null) } "hideTab" -> { val activity = requireActivity() as MainActivity activity.switchBottomView(false) result.success(null) } else -> { result.notImplemented() } } } } }Copy the code

conclusion

Currently native mobile apps can integrate multiple Flutter Modules, making them much easier to use.

Android can also use FlutterView. Multiple FlutterViews can be displayed in an Activity or Fragment. However, using one FlutterView requires binding life cycle, which is slightly complicated.