Sahil Yadav
Sahil Yadav

Reputation: 31

Flutter Ui not update

i am try to create PIP in flutter app, there is no package or plugin that fulfil my requirement. so i decided to create my own logic. what i am doing i am create a new activity in android and start that activity by method call activity start successfully but when i am try update the UI with setState Ui is not update

my Flutter Code:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  static final GlobalKey<NavigatorState> navigatorKey =
  GlobalKey<NavigatorState>();
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      navigatorKey: MyApp.navigatorKey,
      title: 'Flutter PIP Demo',
      initialRoute: "/",
      onGenerateInitialRoutes: (route){
        List<Route<dynamic>> pageStack = [];
        pageStack.add(MaterialPageRoute(builder: (context) => MyHomePage()));
        return pageStack;

      },
      onGenerateRoute: (route) {
        switch (route.name) {
          case "/":
            return MaterialPageRoute(builder: (BuildContext context) => MyHomePage());
          case "live_stream":
            return MaterialPageRoute(builder: (BuildContext context) => MyHomePage2(route.arguments as String ));
          default:
            return MaterialPageRoute(builder: (BuildContext context) => MyHomePage());
        }
      },
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String name = "PIP";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
          child: Center(
        child: ElevatedButton(
            onPressed: () {
              setState(() {
                name = "clicked";
              });
              Navigator.pushNamed(
                context,
                "live_stream",
                arguments: "PIP Screen"
              );
              const MethodChannel("live_stream").invokeMethod("start_new_activity").then((value){
                bool result = value as bool;
                if(result){
                  // Navigator.pop(context);
                }

              });
            },
            child: Text(name)),
      )),
    );
  }
}

class MyHomePage2 extends StatefulWidget {
  final String? name;
  const MyHomePage2(this.name, {Key? key}) : super(key: key);

  @override
  State<MyHomePage2> createState() => _MyHomePage2State();
}

class _MyHomePage2State extends State<MyHomePage2> {

   var methodcall = MethodChannel("live_stream");
   String? name;
   bool con = false;
@override
  void initState() {
  name = widget.name;
  methodcall.setMethodCallHandler((call){
    if(call.method == "onBackPressed"){
      Navigator.pop(context);
    }
    return call.arguments;
  });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
          child: Center(
        child: ElevatedButton(onPressed: () {
          setState(() {
            name = "button clicked";
            con = !con;
          });
          MethodChannel("live_stream").invokeMethod("back_to_main_activity");
        }, child: Text(con? "test" : name!)),
      )),
    );
  }
}

my Main Activity:

package com.example.pip

import android.app.PictureInPictureParams
import android.content.Intent
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Rational
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    private val CHANNEL = "live_stream"
    private var flutterEngine: FlutterEngine? = null
    private var flutterView : FlutterView? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        Log.d("flutter_engine", "flutter")
        super.configureFlutterEngine(flutterEngine)
        this.flutterEngine = flutterEngine
        FlutterEngineCache.getInstance().put("main_flutter_engine", flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                Log.d("flutter", call.method)
                if(call.method == "start_new_activity"){
//                    val flutterView = FlutterView(this)
//                    flutterView.detachFromFlutterEngine()
                    //val intent  =  Intent(this@MainActivity, NewActivity::class.java)
                    startActivity(Intent(this@MainActivity, NewActivity::class.java))
                    result.success(true)
                }else{
                    result.notImplemented()
                }
            }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("Flutter New" , "onCreate")
        flutterView = FlutterView(this)
        flutterEngine = FlutterEngineCache.getInstance().get("main_flutter_engine")
        flutterEngine!!.let {flutterEngine->
            flutterEngine.navigationChannel.setInitialRoute("live_stream")
            FlutterEngineCache.getInstance().put("main_flutter_engine", flutterEngine)
            flutterView!!.attachToFlutterEngine(flutterEngine)
            val flutterLayoutView = findViewById<FrameLayout>(R.id.main)
            flutterLayoutView.addView(flutterView!!)
        }


    }

    override fun onStart() {
        super.onStart()
        Log.d("Flutter Main" , "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("Flutter Main" , "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d("Flutter Main" , "onPause")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d("Flutter Main" , "onRestart")
    }

    override fun onStop() {
        super.onStop()
        Log.d("Flutter Main" , "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("Flutter Main" , "onDestroy")
    }

}

my New Activity:

package com.example.pip

import android.app.PictureInPictureParams
import android.content.res.Configuration
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Rational
import android.view.Surface
import android.view.SurfaceControl
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener
import io.flutter.plugin.common.MethodChannel


class NewActivity : FlutterActivity() {
    private var flutterEngine: FlutterEngine? = null
    private var flutterView : FlutterView? = null
    private val CHANNEL = "live_stream"

//    @RequiresApi(Build.VERSION_CODES.O)
    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new)
        Log.d("Flutter New" , "onCreate")
        flutterView = FlutterView(this)
        flutterEngine = FlutterEngineCache.getInstance().get("main_flutter_engine")
        flutterEngine!!.let {

            it.navigationChannel.setInitialRoute("live_stream")
            FlutterEngineCache.getInstance().put("main_flutter_engine", it)
            flutterView!!.attachToFlutterEngine(it)
            flutterLi(flutterView!!)


            MethodChannel(it.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, _ ->
                Log.d("flutter", call.method)
                if(call.method == "back_to_main_activity"){
                    Log.d("flutter", "onBackPressed")




                    flutterView!!.invalidate()
    //                flutterView!!.detachFromFlutterEngine()
//                    flutterView!!.attachToFlutterEngine(flutterEngine!!)

//                    MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger,CHANNEL).invokeMethod("onBackPressed","Hello")
//                    finish()
                }
            }
            val flutterLayoutView = findViewById<FrameLayout>(R.id.liveStream)
            flutterLayoutView.addView(flutterView!!)
        }
    }


    override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
        if(isInPictureInPictureMode){
            flutterView!!.attachToFlutterEngine(flutterEngine!!)
        }
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onUserLeaveHint() {
        FlutterEngineCache.getInstance().put("main_flutter_engine", flutterEngine)
        val aspectRatio =
            Rational(9, 16) // Set the desired aspect ratio for PiP mode
        val sourceRectHint = Rect(0, 0, 400, 300)
        val params = PictureInPictureParams.Builder()
           params.setAspectRatio(aspectRatio)
           params.setSourceRectHint(sourceRectHint)
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            params.setAutoEnterEnabled(true)
            params.setSeamlessResizeEnabled(true)
        }
            enterPictureInPictureMode(params.build())
        flutterView!!.detachFromFlutterEngine()
        super.onUserLeaveHint()
    }

    private fun flutterLi(flutterView: FlutterView){
        flutterView.addFlutterEngineAttachmentListener(object : FlutterView.FlutterEngineAttachmentListener{
            override fun onFlutterEngineAttachedToFlutterView(engine: FlutterEngine) {
                flutterView.invalidate()
            }

            override fun onFlutterEngineDetachedFromFlutterView() {
                flutterView!!.attachToFlutterEngine(flutterEngine!!)
                flutterView.invalidate()
                Log.d("Flutter New" , "onFlutterEngineDetachedFromFlutterView")
            }

        })
    }

    override fun onStart() {
        super.onStart()
        Log.d("Flutter New" , "onStart")
    }

    override fun onResume() {

        flutterView!!.attachToFlutterEngine(flutterEngine!!)
        super.onResume()
        Log.d("Flutter New" , "onResume")
        flutterView!!.addOnFirstFrameRenderedListener(object : FlutterUiDisplayListener {
            override fun onFlutterUiDisplayed() {
                flutterView!!.invalidate()
                Log.d("Flutter New" , "onFlutterUiDisplayed")
            }

            override fun onFlutterUiNoLongerDisplayed() {
                Log.d("Flutter New" , "onFlutterUiNoLongerDisplayed")
//                flutterView!!.detachFromFlutterEngine()
//
//                // Reattach the Flutter view to the Flutter engine
//                flutterView!!.attachToFlutterEngine(flutterEngine!!)
//
//                // Optionally, you can force a repaint of the Flutter UI
//                flutterView!!.invalidate()
            }
        })

    }

    override fun onPause() {
        super.onPause()
        Log.d("Flutter New" , "onPause")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d("Flutter New" , "onRestart")
    }

    override fun onStop() {
        super.onStop()
        Log.d("Flutter New" , "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        flutterView!!.detachFromFlutterEngine()
        Log.d("Flutter New" , "onDestroy")
    }

}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:label="pip">
        <activity
            android:supportsPictureInPicture="true"
            android:theme="@style/LaunchTheme"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:exported="true"
            android:windowSoftInputMode="adjustResize">
        </activity>

        <activity
            android:supportsPictureInPicture="true"
            android:theme="@style/LaunchTheme"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:name=".NewActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:exported="true"
            android:windowSoftInputMode="adjustResize"/>

        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:exported="true"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">


            <!--
                 Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI.
            -->
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme" />

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--
 Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
        -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>

</manifest>

i try detach and attach the flutter view from flutter engine, its update the UI. But this is not good practice.

Upvotes: 0

Views: 149

Answers (0)

Related Questions