Flutter as a module for Android Developers

Flutter as a module for Android Developers

ยท

3 min read

Flutter has become quite popular among app developers because -

  • Ease of development,

  • Cross-platform,

  • Declarative UI,

  • Hot reload,

  • and Native-like performance.

While we can build a fully functional application using Flutter, it's also possible to add Flutter to a fully functional native android application(and iOS application). In this article, we'll look into exactly that -

We can add Flutter into our existing apps as -

  • Activity

  • Fragment

  • View

In this article, I'll be covering FlutterActivity and FlutterFragment. I'll make another article covering FlutterView using RecyclerView adapter.

In the example code snippets, I'll be using Kotlin.


Setup Flutter module using Android Studio

We'll need to add a new module to our existing project by doing-

First, add the Flutter plugin to Android Studio.

Then in your existing project - File > New > New Module > Flutter Module and then complete the module setup wizard.

ide-wizard.png

Add a single Flutter screen as Activity

In flutter, we inflate a widget (everything in Flutter is a widget) using the runApp function by doing this -

void main() => runApp(const MyApp());

Let's say we want to do the same from our existing app as an activity we can do this -

fun navigateToFlutterActivity() {
        startActivity(
            FlutterActivity
                .createDefaultIntent(this)
        )
    }

Make sure FlutterActivity is imported.

import io.flutter.embedding.android.FlutterActivity

**Result - **

no cache flutter.gif

We'll notice some delay in inflating the widget ๐Ÿ˜•. This is because flutter runs on Flutter Engine which has a non-trivial warm-up time, what we can do is pre-cache the engine and use that to inflate the widget.

For this, we'll use a cached FlutterEngine.

Adding a single Flutter Screen with Cached FlutterEngine as Activity

First, declare the FlutterEngine in the application class as follows -

class AndroidAndFlutterApplication : Application() {
    private lateinit var flutterEngine: FlutterEngine

    override fun onCreate() {
        super.onCreate()
        flutterEngine = FlutterEngine(this)

        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        FlutterEngineCache
            .getInstance()
            .put("engineID", flutterEngine)
     }
}

Engine id parameter can we whatever we want, we'll need it later to uniquely identify the FlutterEngine.

Now we can use this FlutterEngine by simply using .withCachedEngine() builder as -

startActivity(
    FlutterActivity
      .withCachedEngine("engineID")
      .build(this)
  )

**Result - **

cache flutter.gif

Now there is no delay. ๐Ÿ˜Š

Adding a Flutter Screen as Fragment

Similarly, we can add a Flutter screen as a fragment in our existing application with/without a pre-cached engine. Here I'll be using the same FlutterEngine as in FlutterActivity example.

class FlutterFragmentContainerActivity : AppCompatActivity() {
    companion object {
        // Define a tag String to represent the FlutterFragment within
        // this Activity's FragmentManager.
        private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
    }

    private var flutterFragment: FlutterFragment? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flutter_fragment_container)

         // Attempt to find an existing FlutterFragment,
         // in case this is not the first time that onCreate() was run.
        flutterFragment = supportFragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
        if (flutterFragment == null) {
        val newFlutterFragment =
            FlutterFragment.withCachedEngine("engineID")
            .build<FlutterFragment>()
        flutterFragment = newFlutterFragment

        supportFragmentManager
            .beginTransaction()
            .add(
                R.id.fragmentContainer,
                newFlutterFragment
            )
            .commit()
        }
    }

Make sure FlutterFragment is imported.

import io.flutter.embedding.android.FlutterFragment

Navigating to a specific Flutter widget

Let's see we don't want to open the default Fluttermain() => runApp(), instead we want to specify some entry point to inflate.

We can do that too. First, we need to define a pragma that indicates the Dart compiler that our function will be used in native code. Inmain.dart define -

@pragma('vm:forSpecificRoute')
void specificRoute() => runApp(const AppWithSpecificRoute());

Now we'll define a FlutterEngine and pass the

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

        // Start executing Dart code to pre-warm the FlutterEngine.
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint(
                FlutterMain.findAppBundlePath(), "specificRoute"
            )
        )

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

Now we can use this FlutterEngine to inflate a specific widget.


I hope you found this article useful, you can find the source code here.

Follow me for more articles like this. Have a great day! ๐Ÿ˜Š

Did you find this article valuable?

Support Abhishek Kumar by becoming a sponsor. Any amount is appreciated!