Picture in Picture Mode in Flutter in Android: Implementation and Tips

I want to show you how to implement Picture-in-Picture mode.

For that, I'm using this plugin called floating, as you can see. This is its official page:

https://pub.dev/packages/floating/example

And below is an example, which—in my humble opinion—is a bit confusing. I'll show you my own implementation, which I find clearer.

Also, remember to enable PIP mode on Android:

android\app\src\main\AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:label="DL: Cursos/Libros"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">


        <activity
            android:name=".MainActivity"
            + android:supportsPictureInPicture="true"
            ***

Custom class: CustomPIP, for the PIP

It all starts with a class called CustomPIP, where I define features such as:

  • PIP mode.
  • Body mode (full mode).

In my case, for example, when I'm in the Academy app and select one of the courses I've already purchased, that full course becomes the "body."

Here I activate Floating, configure its widget-specific options, and within the StatefulWidget, I check if it's enabled, since not all Android systems allow it. So, you have to first check if it's available.

class CustomPip extends StatefulWidget {
  final Widget body;
  final Widget pip;

  late PointADS point;

  final floating = Floating();

  activatePIP() {
    floating.enable(const ImmediatePiP());
  }

  CustomPip({super.key, required this.body, required this.pip});

  @override
  State<CustomPip> createState() => _CustomPipState();
}

class _CustomPipState extends State<CustomPip> {
  bool canUsePiP = false;

  @override
  void initState() {
    if (Platform.isAndroid) {
      // verifica si el pip esta disponible
      widget.floating.isPipAvailable.then((value) {
        canUsePiP = value;
        setState(() {});
      });
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    if (Platform.isAndroid) {
      // verifica si el pip esta disponible
      return PiPSwitcher(
        childWhenDisabled: widget.body,
        childWhenEnabled: widget.pip,
      );
    }

    return widget.body;
  }
}

I save that information for later use. Then I activate the switcher (as they call it), which basically determines what the body (the entire page) is and what the content will be in PIP mode.

Integrating the PIP into the detail view

Using it is very simple. This is the configuration I created, and what I recommend.

On the detail page (the one you're seeing on the screen), I have all the normal settings. What I'm interested in here is saving an instance of the player, which will be placed in PIP mode.

For example: here, if I don't activate PIP mode correctly, only the maximize option appears. So, you have to leave it enabled beforehand.

Instantiating CustomPIP with your view
Here I create an instance of CustomPIP, to which I pass the entire scaffold, that is, the complete structure of your widget, which can be any view. I also pass the content I want to keep in PIP mode:

  _initCustomPIP() {
    videoReproductor = VideoTutorialReproductor(
      paymentType: tutorialModel.payment?.type ?? '',
      tutorialSectionClassModel:
          tutorialModel
              .tutorialSectionsModel[appModel.selectedSectionIndex]
              .tutorialSectionClassesModel[appModel.selectedClassIndex],
      typeVideo: tutorialModel.type,
    );

    customPIP = CustomPip(
      pip: videoReproductor,
      body: Scaffold(
        ***
    );
  }

Then I create this property (customPip) and use it inside the build method. It's that simple.

The widget redraws when entering/exiting PIP mode

When we're in PIP mode, we can't really modify anything that's playing with this plugin. It has to maintain what it was doing before, that is, the widget's status.

A key point: you have to preserve the state. Usually, when switching from PIP mode to full mode (and vice versa), the widget tends to redraw completely, so you need to implement logic to prevent this.

I agree to receive announcements of interest about this Blog.

I show you how to implement Screen-in-Screen mode from a Flutter app.

- Andrés Cruz

En español