Daniel Cunha
Daniel Cunha

Reputation: 676

Flutter multiscreen using DisplayManager or MediaRouter

I'm developing an app in Flutter which is supposed to run on a POS with two screens. It's basically a tablet connected to another screen via a HDMI cable.

My problem is that whatever I run on my side of the screen also appears to the client's screen, and I need to hide it and show other content (as promotions for example).

I've already managed to use it using Android Presentation API (it will only run on Android). And I've read somewhere that I also can achieve it using Media Router.

But what I want to do is to run two engines of Flutter, one on each display, or control what is shown in each display in Flutter. The problem is I didn't found any library or useful article of how to do it on Flutter.

Does someone know how I would achieve that? If I don't make it, I'll have to create a whole new project using native Android.

Upvotes: 3

Views: 3013

Answers (2)

zero.one
zero.one

Reputation: 1553

I think that one of the approaches is to create two flutter apps

the first one is the POS application the second one is the client side application

and connect both of them on a websocket server on the POS side (or any other deivces but the point is to make sure that both of them are connected to the same websocket server so they can communicate with each other)

Upvotes: 1

Daniel Cunha
Daniel Cunha

Reputation: 676

For those who perhaps is facing this issue, I finally figured out how to manage it. I'm still using Android Presentation API, and now I'm creating a new engine for client display. I solved it using FlutterView, which is pretty new yet. It's basically used for rendering a Flutter in a Native app. But in our case it will render our project in another screen.

The only problem I didn't solve (yet) is that the two engines are completely different applications. So, if you are using Provider for managing your app state for example, you'll have two instances or more, one for each instance, and changes made in one won't reflect the others.

In my case I will solve it using Socket.io, but you'd probably also solve it using Method Channel. Ideally, it should have only one state, but for now that's what I've got.

Here's my code for solve this issue:

MainActivity.kt in android/app/src/main/kotlin

package com.example.fsj_pdv

import android.content.Context
import android.hardware.display.DisplayManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setSecondDisplay()
    }

    private fun setSecondDisplay() {
        try {
            // Versions before Jelly Bean doesn't support it
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                val manager = applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager

                // Busca os monitores disponíveis
                val displays = manager.displays

                // Verifica se foi encontrado mais de um monitor. Caso encontre, instancia a Activity de Presentation
                if (displays.size > 1) {
                    val display = displays[1]
                    val handler = PresentationHandler(this, display)
                    handler.show()
                }
            }
        } catch (e: Throwable) {
            println(e.message)
            e.printStackTrace()
        }
    }
}

And here's the class I've created to solve my problem (in the same folder). The purpose of this class is basically to setup our Presentation display and choose the content view which will have our FlutterView.

package com.example.fsj_pdv

import android.annotation.TargetApi
import android.app.Presentation
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.Display
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint


@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
class PresentationHandler(outerContext: Context?, display: Display?) : Presentation(outerContext, display) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Instantiate a FlutterEngine.
        val flutterEngine = FlutterEngine(context)

        // Configure an initial route.
        flutterEngine.navigationChannel.setInitialRoute("/cliente")

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

        setContentView(R.layout.presentation_view)

        val flutterView: FlutterView = findViewById(R.id.flutter_presentation_view)
        flutterView.attachToFlutterEngine(flutterEngine)
    }
}

Finally, create a simple layout which will render our application in the second/external display. Here's my layout (presentation_view.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <io.flutter.embedding.android.FlutterView
        android:id="@+id/flutter_presentation_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        />

</LinearLayout>

Upvotes: 5

Related Questions