Do applications on Flutter dream about platform-oriented widgets?

Hi, Habr! I present to you the translation of the article “Do Flutter apps dream of platform aware widgets?”

When developing applications on Flutter with native design for iOS and Android, I was faced with the fact that I had to write a bunch of conditions to check the platform on which the code is being executed, while two similar implementations of UI. I did not like it and I am glad that I came across an article that helped me solve my problem.

About the author: Swav Kulinski - Lead Android Developer in The App Business, Flutter GDE.
Next, it will be on behalf of the author.

Flutter is a cross-platform mobile application development solution that promises absolute freedom in creating a user interface regardless of platform. This is achieved by the fact that the framework uses its own rendering engine for drawing widgets.

The problem with many cross-platform solutions is that they look the same on the iPhone and Android. But what about companies that need to maintain the look and feel of the platform? Who should use Material Design for Android and Human Interface for iOS? For such companies, Flutter is suitable, equipped with packages that contain a set of custom widgets for iOS and Android, called Cupertino and Material.



Flutter is cross-platform in nature, but when it comes to the UI layout, which should look native to each of the platforms, this is not exactly the case. We have to do two similar implementation of the layout. This is due to the fact that, for example, for IOS, CupertinoNavigationBar must be in CupertinoPageScaffold, and in Android, AppBar inside Scaffold. This feature reduces the cross-platform advantage in Flutter, since for each platform you have to write your own code for typesetting.

I would like to suggest an approach that allows you to create abstract interfaces and adjust the appearance and behavior of the application, depending on which platform it is running.

Consider the following constructors for two widgets that provide the top application bar:

CupertinoNavigationBar ({
    this.leading,
    this.middle,
})

and

 AppBar ({
    this.leading,
    this.title
})

Both of the above mentioned widgets play the same role, being the top panel in the Cupertino and Material style apps. But still they require input in a different form. We need a solution that will abstract away from how we create specific widgets, while still ensuring implementation depending on the platform. We use the good old factory method.

import 'package:flutter/material.dart';
import 'dart:io' show Platform;
abstract class PlatformWidget<I extends Widget, A extends Widget>
    extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (Platform.isAndroid) {
      return createAndroidWidget(context);
    } else if (Platform.isIOS) {
      return createIosWidget(context);
    }
    // platform not supported returns an empty widget
    return Container();
  }
  I createIosWidget(BuildContext context);
  A createAndroidWidget(BuildContext context);
}

In essence, the above class is a factory of platform-dependent widgets, which, when implemented, can provide a custom constructor (or several named constructors) that support the needs of both specific classes.

I chose generics to return specific classes, because sometimes the parent widget needs a certain type to be returned from the child widget.

Now we can implement our first widget.

 class PlatformAppBar extends PlatformWidget<CupertinoNavigationBar, AppBar> {
  final Widget leading;
  final Widget title;
  PlatformAppBar({
    this.leading,
    this.title,
  });
  @override
  AppBar createAndroidWidget(BuildContext context) {
    return AppBar(
      leading: leading,
      title: title,
    );
  }
  @override
  CupertinoNavigationBar createIosWidget(BuildContext context) {
    return CupertinoNavigationBar(
      leading: leading,
      middle: title,
    );
  }
}

Pretty simple, right? Please note that we completely control the contents of the application panel widgets.

Suppose we implemented Scaffold and Button.

class PlatformScaffoldWidget extends PlatformWidget<CupertinoPageScaffold,Scaffold> {
    ...
}
class PlatformButton extends PlatformWidget<CupertinoButton,FlatButton> {
    ...
}

Now we are ready to use and reuse our platform-oriented widgets.

Widget build(BuildContext context) {
  return PlatformScaffoldWidget(
      appBar: PlatformAppBarWidget(
        leading: PlatformButton(
            child: Icon(Icons.ic_arrow_back),
            onClick: () => _handleBack()
        ),
        title: Text("I love my Platform"),
      ),
      content: ...
  );
}

Done! The above code will display the platform-oriented application panel on both platforms, and our PlatformScaffoldWidget is ready to be reused in the rest of the application without any problems.

The code can be viewed on the githaba.

Also popular now: