4

Flutter for Apple TV

 2 years ago
source link: https://medium.com/flutter-community/flutter-for-apple-tv-756fcd5e8113
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Flutter for Apple TV

In March 2021, Flutter got a major upgrade that enables developers to create beautiful, fast, and portable apps for many different platforms. With Flutter 2.x, you can use the same codebase to ship native apps to mobile operating systems such as iOS, and Android, to desktop operating systems like Windows, macOS, and Linux, as well as to browsers such as Chrome, Firefox, Safari, or Edge. Also, the Flutter team gave a little information about Flutter for embedded devices, but nowhere it was officially described how you can develop applications for smart TV operating systems using Flutter.

If you try to search in StackOverflow or GitHub for information on the topic of Flutter for TV, you can find pieces of scattered information from the community, mostly about experiments with Android TV, and practically nothing about other platforms. The reason for this is that Android TV is not a Flutter platform supported for production usage, and Apple TV is not supported at all.

But nonetheless, if you look in the Flutter repository on GitHub, you can find a lot of issues associated with an app’s development for Smart TVs, and not even only Android TV, but for Apple TV as well. We can conclude that despite the fact that there is no official support, developers continue trying to create applications for smart TVs.

Actually, I’m not an exception. My team’s task was to create an application for several platforms at once, including Smart TV Platforms. We had to support seven platforms with the same codebase: iOS, Android, Web, Android TV, Apple TV, Fire TV, and Tizen. We turned out to be the developers who were trying to create an application not only for mobile and web but also for TV platforms.

That is why I decided to write this article in order to share some experience that we’d gained during development, what difficulties we’d encountered, and what bumps we’d got.

But the first question that should arise is “why”, why you might even need to use Flutter to create TV applications. I have several answers to this question. Firstly, it’s very cool if you can just take an already written Flutter application and just run it on the TV. Secondly, and much more important, if you are planning to develop an application that will support TV platforms, Flutter allows you to save a lot of resources.

Actually, the more platforms using a single code-base you support, the more resources you save. It isn’t necessary to have separate development teams for TV applications, everything could be done by the same team. And this is great! 🥳

In this article, I’m going to show in detail how to run Flutter applications for Apple TV, and the difference between Android TV and Apple TV when working with these platforms.

So, it’s always better not just to say words, but also to show an example on a real application. Unfortunately, I cannot share examples from the application we are working on, since this contradicts the NDA, so I’ve prepared an example of a simple application that works on mobile devices, web browsers, and TV devices as well. It’s just a gallery of movies and TV series. If you tap or click on any of the movies, a page with a detailed description will open.

iOS, Web, Android

There is a result of running this application on an iPhone, an Android tablet, and in the Chrome browser.

Let’s try running it on Android TV.

Actually, with the launch of Android TV, everything is extremely simple, because Android TV is the same Android OS. You can start the Android TV emulator, or connect a real device via Android Debug Bridge (adb), and click the run button. The application just starts, without any additional actions.

But you need to take into account one detail, TV layout and mobile layout sometimes have differences, and it’s necessary to have an opportunity to distinguish between them. There is Platform class in Flutter, which allows to determine where the application is launched. So we have the issue here: Platform.isAndroid will be true for both mobile and tv, because Android TV is also Android. You need to provide additional information about the platform during the build. This can be done in several ways: making different entry points, for example, like ‘main.dart’ and ‘main_tv.dart’. But it seems to me that the easiest way is to use Flutter Environment Variables. In the example, I created a TV_MODE environment variable to provide different values for the compiler: ‘ON’ for TV and anything else for mobile. Then I implemented MyPlatform class and used it instead of Platform.

class MyPlatform {
static const tvMode = String.fromEnvironment('TV_MODE');
static bool get isTv => tvMode == 'ON';

static bool get isIOS => !isTv && Platform.isIOS;
static bool get isAndroid => !isTv && Platform.isAndroid;
static bool get isTVOS => isTv && Platform.isIOS;
static bool get isAndroidTV => isTv && Platform.isAndroid;
}

So, don’t forget to provide TV_MODE=ON when launching the app on the TV, to get the correct behavior of the example app.

flutter run --dart-define TV_MODE=ON

So, let’s check.

Android TV

And what about Apple TV?

Despite the fact, that an Apple TV simulator or real device can also be selected as a target device in Android Studio, a Flutter application cannot be run on it.

Let me explain why.

If you look at the structure of the Flutter project, you can see different folders for ios and for android applications. So, there is an Android project in the ‘android’ folder, and there is a prepared XCode project for the iOS application in the ‘ios’ folder, but Apple TV has a different operating system — tvOS. And as you can see, there is no tvOS folder in the project, and Flutter can only run on iOS.

The project structure

Actually, you can try to open this project in XCode and edit it, I mean, change the deployment target to tvOS, change a storyboard, and so on, in order to make it a tvOS project. But even if you do this, the flutter application will still not start anyway.

The point is that tvOS is not iOS, it is a different operating system, quite close to iOS in its structure, and it has the same core — Darwin. But there are differences, at least in API, a part of the iOS API is simply absent in the tvOS, so the Flutter Engine will fall when building a tvOS application, trying to find non-existing methods.

But there is a solution. Flutter engine is completely open-sourced, which means that it is potentially possible to adapt it to build projects for tvOS. There is a detailed manual on how to get the source code of the Flutter engine and set up your environment for development, and there are manuals on how to compile and how to debug your updated engine.

You can try to make necessary changes: cut out using of methods that are absent from tvOS and edit calls of methods whose signatures are different. But you need to have some experience with C and Objective-C.

It would be an interesting experience, but it is not really necessary. There are enthusiasts who have already made these changes and open-sourced the updated Flutter Engine. Actually, I’m one of them. But you still need to compile the engine locally, following the manual mentioned above, or you can take a look at the detailed manual, created especially for the Flutter for Apple TV approach, which also can help.

Finally, you can just download the already built by myself custom Flutter Engine (supported Flutter version is 2.10.3), but be careful, the archive file is heavy, about 6.5 GB.

You can check how it works using my example, following the steps below:

  1. Clone the example
  2. Download the engine
  3. Change FLUTTER_LOCAL_ENGINE value by the path to the downloaded engine folder in scripts/run_apple_tv.sh
  4. Choose the necessary type of engine (debug_sim, debug or release) in scripts/run_apple_tv.sh
  5. Execute sh scripts/run_apple_tv.shin the terminal window(when script ends, XCode with AppleTV project will be opened)
  6. Choose a device and click the ‘Run’ button in opened XCode project

Voila! The application should start on the Apple Tv device or the simulator, depending on your choice.

Apple TV

So we’ve figured out how to run Flutter applications on Apple TV. But just launching the application is not quite enough. There are many different questions. For example, how do you organize the interaction with a user? You can’t tap the TV screen with your finger, and you don’t even do a mouse click, like in a browser, you have to interact with a TV using a remote control unit.

In the case of Android TV, everything is quite simple: a Remote Control Unit is connected to the device as a raw keyboard, and events of pressing the buttons on the remote are processed as pressing the keys on the keyboard. Up, down, left and right buttons are the same as arrow keys on the keyboard. Flutter has already got the mechanism that handles keyboard events. This is the Focus widget. The widget manages FocusNode to allow keyboard focus to be given to the widget and its children. Usually, it is used to navigate between widgets using the keyboard, for example in the browser. You just need to implement highlighting of focused widgets.

In the case of Apple TV, there is a big difference. The standard Apple TV remote control unit has no arrow buttons at all, touchpad only. And it’s necessary to support not only taps and clicks on a touchpad, but swipes as well.

Android TV (MiBox) vs Apple TV RCU

But for Apple TV support, you need to use the custom Flutter engine, which means you can make changes there. So for receiving touchpad events on the Flutter side, it’s just necessary to add some code to the Engine that provides information about taps, clicks, and swipes to the Flutter app through Platform Channels.

Actually, in the open-sourced custom Flutter engine that you have compiled (or downloaded) before, this functionality is already implemented. Taps on left, right, up, and down sides, are being transformed to raw keyboard events automatically as pressing arrow keys, the only thing left for you to do is to handle swipes and clicks in your Flutter application.

The custom engine provides flutter/gamepadtoucheventPlatformChannel, that gives an opportunity to handle touchpad events. You can create your own touchpad controller to manage focus. The channel provides three arguments: ‘x’, ‘y’ and ‘type’. The first two are coordinates of your finger on the touchpad, and the third one is a type of interaction. It can take the values: ‘started’, ‘move’, ‘ended’, ‘loc’, ‘click_s’ and ‘click_e’ which mean:

  • started: the finger is on the touchpad, and the swipe is started;
  • move: the swipe in the process;
  • ended: the swipe is ended, the finger released the touchpad;
  • click_s: the finger is on the touchpad, the click is started;
  • click_e: the finger released the touchpad, the click is ended;
  • loc: just coordinates of the finger, not depends on interaction;

There is an example of how you can implement the simple touchpad controller. There, the movement of the finger by 400 pixels in some direction is caught and triggerKey method is called.

static const channel = BasicMessageChannel<dynamic>('flutter/gamepadtouchevent', JSONMessageCodec());

void init() {
channel.setMessageHandler(_onMessage);
}

Future<void> _onMessage(dynamic arguments) async {
num x = arguments['x'];
num y = arguments['y'];
String type = arguments['type'];
late LogicalKeyboardKey key;

if (type == 'started') {
swipeStartX = x;
swipeStartY = y;
isMoving = true;
} else if (type == 'move') {
if (isMoving) {
var moveX = swipeStartX - x;
var moveY = swipeStartY - y;

// need to move min distance in any direction
// the 400px needs tweaking and might needs to be variable based on location of the widget on screen and duration/time of the movement to make it smoother
if ((moveX.abs() >= 400) || (moveY.abs() >= 400)) {
// determine direction horizontal or vertical
if (moveX.abs() >= moveY.abs()) {
if (moveX >= 0) {
key = LogicalKeyboardKey.arrowLeft;
} else {
key = LogicalKeyboardKey.arrowRight;
}
} else {
if (moveY >= 0) {
key = LogicalKeyboardKey.arrowUp;
} else {
key = LogicalKeyboardKey.arrowDown;
}
}
triggerKey(key);
// reset start point (direction could change based on next cooordinates received)
swipeStartX = x;
swipeStartY = y;
}
}
} else if (type == 'ended') {
isMoving = false;
} else if (type == 'click_s') {
unawaited(
simulateKeyEvent(
PhysicalKeyboardKey.enter,
isDown: true,
),
);
} else if (type == 'click_e') {
unawaited(
simulateKeyEvent(
PhysicalKeyboardKey.enter,
isDown: false,
),
);
}
}

The triggerKey is a method that triggers FocusManager to move the primary focus in the necessary direction.

void triggerKey(LogicalKeyboardKey key) {
if (LogicalKeyboardKey.arrowLeft == key) {
FocusManager.instance.primaryFocus!.focusInDirection(TraversalDirection.left);
} else if (LogicalKeyboardKey.arrowRight == key) {
FocusManager.instance.primaryFocus!.focusInDirection(TraversalDirection.right);
} else if (LogicalKeyboardKey.arrowUp == key) {
FocusManager.instance.primaryFocus!.focusInDirection(TraversalDirection.up);
} else if (LogicalKeyboardKey.arrowDown == key) {
FocusManager.instance.primaryFocus!.focusInDirection(TraversalDirection.down);
}
}

One more thing not to forget is that the custom engine doesn’t support pressing the back RCU button out-of-the-box, you have to support it on your own. You should handle this event and send the popRoute message event to the navigation system channel.

void init() {
HardwareKeyboard.instance.addHandler(handleKeyMessage);
}

bool handleKeyMessage(KeyEvent message) {
if (LogicalKeyboardKey.goBack == message.logicalKey) {
final message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
ServicesBinding.instance!.defaultBinaryMessenger
.handlePlatformMessage(SystemChannels.navigation.name, message, (_) {});
return true;
}
return false;
}

These were the most basic points regarding RCU support for Apple TV.

That’s it, congratulations! You’ve run your first Flutter app for AppleTV and now you understand that it is possible!

Flutter for Apple TV simple demo

So, the development of TV applications using Flutter is not something unrealistic. We have considered AndroidTV and AppleTV, but there are more TV platforms, such as Tizen, FireTV, WebOS, RokuTV and so on. I’m sure, that Flutter can be used for the development of applications for them as well, and we’ll definitely discuss it in future articles.

https://twitter.com/FlutterComm


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK