Amazon.co.uk Widgets

Log in

X
Screenshot of FlutterFlow Custom Action editor
FlutterFlow Custom Action editor, showing a 'Future' written in 'Dart'

I needed a way to get the latest video from a particular YouTube channel for an app which I am working on in FlutterFlow. I thought it would be pretty trivial. I had no idea how far down the YouTube, XML and Dart custom action rabbit hole I would have to go. But I persevered and cracked it and thought it might be worth writing up in case anyone else is trying to do something similar.

This detail page in my app is, on the face of it, very simple. Part of it comprises a container, which is populated with data from Google Firebase, in order to make the elements of the container engaging and useful. You can tap the YouTube Video to play it, tap the buttons to invoke the relevant engagement methods with the entity for which the information has been populated from Firebase. The YouTube Player component from FlutterFlow is very nice, and all it needs is the URL to the video to work with.

A FlutterFlow container based widget comprising a YouTube player, and button links to Twitter, YouTube, Facebook and phone call
Container for YouTube Player and engagement method buttons

A FlutterFlow container based widget comprising a YouTube player, and button based links in a row to the engagement methods of Twitter, YouTube, Facebook and phone call.

Screenshot from FlutterFlow designer

A FlutterFlow container based widget comprising a YouTube player, and button links to Twitter, YouTube, Facebook and phone call
FlutterFlow Widget tree for YouTube Player and engagement method buttons

The widget comprises a ListView which gets a document from a collection in Firebase, and a container with a column and text widgets to display it. The buttons have actions which are populated by data from Firebase

Screenshot from FlutterFlow designer

In the end this became a bit of a journey into FlutterFlow custom actions, pubspec dependencies, dart programming and XML. It took me far too long to do because I needed to learn each thing sequentially and then go back and iterate again adjusting my bad understanding of things until finally I cracked it and it works reliably and automatically.

YouTube RSS feeds

Did you know YouTube make an RSS feed available? Neither did I. That is because I suppose they want you to use their player and their website to play videos. The RSS feed has been part of YouTube since they began operations and though I expect the powers that be would like to deprecate RSS, it must be far do difficult to disentangle it from the site now. So lets look at it. But how do you get to it?

YouTube Channel ID

Turns out you need to know the YouTube Channel ID in order to get the feed link. This can be buried deep in the YouTube site. An example is this one https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw the channel ID is the last part of the link. In this case UCuAXFkgsw1L7xaCfnd5JJOw.

Vanity YouTube channel links don't contain the channel ID, and neither do specific TouTube video links but theres a script (the link is at the bottom of this page) which can determine the channel from any given YouTube video link. Simply go to the video, click share, and paste the link as a parameter for the script. You'll get back the full RSS feed URL which can now be used for our purposes.

Here's an example, you just need to run the script with the link to a YouTube Video and it will tell you the correct feed link which contains the channel ID.

% ./youtube-rss.sh  https://youtu.be/dQw4w9WgXcQ
https://www.youtube.com/feeds/videos.xml?channel_id=UCuAXFkgsw1L7xaCfnd5JJOw

YouTube RSS

The RSS feed link is in a standard format with the channel_id as the parameter. This link contains the information needed for my FlutterFlow YouTube player. It returns structured data from which it is possible to determine the unique video Identifier for all the YouTube videos in the feed.

Inside the <entry> tags you can see a video identifier inside the <yt:videoId> tags. For example <yt:videoId>LLFhKaqnWwk</yt:videoId>. This means that we can find video identifiers programatically, and if we can find them, we can find the one we want, in this case the latest one. Remember, the reason for doing this work at all is to place it in the right place as a variable so that the FlutterFlow player can play it!

Here an excerpt of the entry section of a YouTube RSS (XML) feed.


<entry>
<id>yt:video:LLFhKaqnWwk</id>
<yt:videoId>LLFhKaqnWwk</yt:videoId>
<yt:channelId>UCuAXFkgsw1L7xaCfnd5JJOw</yt:channelId>
<title>Rick Astley - Never Gonna Give You Up (Official Animated Video)</title>
<link rel="alternate" href="https://www.youtube.com/watch?v=LLFhKaqnWwk"/>
<author>
<name>Rick Astley</name>
<uri>https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw</uri>
</entry>

The YouTube video player in FlutterFlow

The FlutterFlow YoutubePlayer widget is a 'base element' in FlutterFlow indicting that it is an imporant key feature for FlutterFlow and is maintained as part of the FlutterFlow project. You can find it in the Base Elements section of the App Designer. (in the Widget Panel) or add it directly from the widget tree.

Screenshot of the YouTube video player in FlutterFlow app designer showing the Widget Tree and the YouTube Player parameters.
The YouTube video player in FlutterFlow app designer showing the Widget Tree and the YouTube Player parameters.

In addition to the usual FlutterFlow app designer controls, te YouTube Player widget allows you to set the URL, whether to loop, Mute, allow the YouTube controls, and allow full screen. You can see how I have implemented it in FlutterFlow, to use a 'Text Combination' variable as the input. This is beause the URL from my custom action (more abut that later) is not quite in the right format for the control, and adding this wrapper to it fixed the issue.

Screenshot of the Combine Text expansion dialog from the URL field in the FlutterFlow YouTube player
The Combine Text expansion dialog from the URL field in the FlutterFlow YouTube player

You can see the Combine Texts has just one value, from the variable ytVid and this is the variable that is created by my custom action.

FlutterFlow Custom Action to populate YouTube URL

The whole point of this exercise is to get the latest YouTube video each time you visit this page. It becomes a far more engaging page if it is right up to date. Even with a small number of these detail pages managing the latest Video would quickly become impossible. I thought initially about writing a server side action in Firebase but it seemed to me to be far easier to do it on demand on the device when the page is loaded. This works well, is reliable and quite fast enough, and will not consume any cost in Firebase. It does however require the device to be online. I used to worry about this a lot for mobile apps but cellular or wifi data on phones is ubiquitous now and I try to govern it with an overall internet connection checker for each page rather than and inside each individual widget.

The secret to having the results of a custom action available in a details page in FlutterFlow

It may seem like the opposite approach to you, but the FlutterFlow action that creates the latest video is not on this page at all. It is initated by the calling page, as an On Tap action, just before the Navigate To action that opens this details page. Heres the custom action in the Actions Editor.

On Tap Action

Screenshot of FlutterFlow actions editor showing a custom action then a navigate to action
The FlutterFlow actions editor showing a custom action then a navigate to action

The action has an single argument idChannel which is a variable from our firebase document containing the Youtube Channel iD for the entity to which the detail page is about and a single Action Output variable name ytVid which will contain the latest video URL for the YouTube Player on the next screen.

Page parameters

The second action, which navigates to our destination details page, contains the page parameters including the ytVidfor the YouTube player to use.

Screenshot of the Page parameters which are being passed to the destination page
Page parameters ready to be passed to the destination page

Why build the custom Action code, in Dart first?

The FlutterFlow app development and design environment is not a no-code tool. No code tools are almost always too inflexible to be useful apart from in very limited enterprise scenarios and should generally be avoided.

This means that you can, if you wish, harness the power of any published component or write any functionality you care to provide. I'm not afraid to admit I am a novice with Dart, and FlutterFlow allows me to limit my use of custom code to very specific areas of my app, so that I dont spend hours debugging missing semi-colons far away from the task I am attempting to accomplish and wondering about the placement and wrapping in my app structure to best implement my code. I find that to be a tremendous freedom. It hugely accelerates my productivity but it is not without its own particular issues.

I think it is worth approaching building custom actions for your FluuterFlow apps in the same way as you would go about building a Flutter or Dart based app. I found it helpful to test out my app as a plain old Dart app from the command line so as to make sure of my logic. This perhaps isn't completely necessary, but for me it removed all the moving parts of Flutter while I got to grips with learning enough of Dart to make my YouTube video link ID extraction work. YMMV.

Dart ytxml

ytxml is a simple command-line application which gets the RSS feed from a given YouTube Channel ID and finds the latest video!

pubspec.yaml

name: ytxml
description: A simple command-line application.
version: 1.0.0
# homepage: https://www.ezone.co.uk/flutter/youtube-xml-flutter-latest.html

dependencies:
  xml: ^5.3.0

ytxml.dart

//
//      Copyright 2022 Multizone Ltd. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
//      copyright notice, this list of conditions and the following
//      disclaimer in the documentation and/or other materials provided
//      with the distribution.
//    * Neither the name of Multizone Ltd. nor the names of its
//      contributors may be used to endorse or promote products derived
//      from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
//  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import 'dart:convert';
import 'dart:io';
import 'package:xml/xml.dart';

// To run in Dart from the command line an app needs a 'main' so this Future is uncommented.
Future<String> main(String idChannel) async {
  if (idChannel.isEmpty) {
    print('Was expecting a YouTube Channel ID as a parameter');
    return ("No YouTube Channel ID");
  }

// A FlutterFlow Custom Action cannot have a 'main' as one already exists
// comment out the dart Future and if sttement and uncomment this to use in FlutterFlow
// Future<String> latestVideo(String idChannel) async {
  // Get latest YouTube Video Id from Youtube RSS api
  var idString = idChannel.toString();
  print (idString); //Check we have the parameter correctly set. Remove for production.
  final HttpClient httpClient = HttpClient();
  final url = 'https://www.youtube.com/feeds/videos.xml?channel_id=$idString';
  final request = await httpClient.getUrl(Uri.parse(url));
  final response = await request.close();
  final stream = response.transform(utf8.decoder);
  final contents = await stream.join();
  final document = XmlDocument.parse(contents);
  var vids =
      document.findAllElements('yt:videoId').map((node) => node.text).toList();
  var firstVideo = 'https://youtu.be/' + (vids.first);
  print (firstVideo); //Check we have got the video linkr correctly. Remove for production.
  return (firstVideo);
}

This little app is asynchrous using a Dart Future. You can find the codelab on Asynchronous programming: futures in the link at the bottom of this article. It allows the XML dart package to parse the feed for the provided YouTube channel ID, extracts the YouTube video identifiers and then it uses the dart .first element property to get the first video ID (which I think is always the latest). It becomes the firstVideo variable which is printed to the console (or handed back to FlutterFlow as the return string from the custom action).

Dart and FlutterFlow can use the print(); function for logging, but this is discouraged in production apps and although you could call the debugPrint(); function to display things in the console in FlutterFlow this relies on flutter/foundation.dart import 'package:flutter/foundation.dart'; to function and this is not available for a pure dart program.

Our Dart app requires a command line parameter - the YouTube Channel Identifier. Flutter has this parameter passed automatically to the FlutterFlow custom action.

FlutterFlow Custom action

Paste the working Dart code into your custom action. Comment out the Dart main Future, and uncomment the Flutter Future. Set the FlutterFlow Action name to latestVideo and set it to have a return value, a String. Define an argument idChannel and set your depencencies. we need convert: ^3.0.2 and xml: ^5.3.0 due to other dependencies in FlutterFlow apps.

Screensot of the code and arguments and depenencies for the YouTube parsing FlutterFlow Custom Action
The code, arguments and depenencies for the YouTube parsing FlutterFlow Custom Action

You'll need to compile your code inside FlutterFlow. Ignore the dependencies errors. FlutterFlow doesn't know what they are and doesnt count them as errors. Once you clear all errors, you can download and run and it will 'just work'. It took me a fair while to debug. If it doesnt work first time, you can load up your local copy of the FlutterFlow app source code and see where it fails. The main issues I faced (other than not understanding dart), were matching the dependencies to allow FlutterFlow to work. As of this writing xml: ^5.3.0 is the maximum FlutterFlow can support. This is easy to spot in a regular Flutter debutting tool (I use VSCodium which is a community-driven, freely-licensed binary distribution of VSCode and you can find out more about it in the links below). In VSCodium you can also see any print(); statements you have added in the developer logs.

Debugging with FlutterFlow Flutter based apps with VSCodium

Here you can see my app running on a real Google Android device attached over USB. The debug console shows me the idChannel I passed to my custom code, and the latestVideo URL I returned safely to FlutterFlow to put into its YouTube player.

Screenshot of VSCodium running this FlutterFlow based Flutter app locally with a real Android device. It illustrates nicely how you can see the variables both in the debug console and the Dart development tools
VSCodium running this FlutterFlow based Flutter app locally with a real Android device. It illustrates nicely how you can see the variables both in the debug console (at the bottom in Blue, filtered by I/flutter and the Dart development tools (on the right the two grey stdout entries show the same information.

 

See also

A script to get the RSS feed for a YouTube page by Timothy J. Luoma.
The FlutterFlow YoutubePlayer widget which is used to play a video from Youtube. 
Dart.Dev - Get started: Command-line and server apps 
Dart.Dev - Codelab on Asynchronous programming: futures, async, await 
VSCodium and Flutter on this website