Learning Flutter
Wait, what is Flutter™ ?
A framework for simultaneous publication of apps for Apple and Google Android devices
Flutter is an open source framework by Google for building beautiful, natively compiled, multi-platform applications from a single codebase.
I create mobile applications that can be published in the App Store and Google Play. I'm not a hard core developer, I have professional developers working with me in my clients and contracted work. But I am a product manager / interim CTO.
It seemed to me that going through the whole process of app creation and management and documenting it for others would be useful. I've been writing requirements and producing mobile applications for decades but I thought I'd like to learn at a far deeper level about Flutter just for fun and because cross platform applications are something I have a passion for. This is my learned experience of picking up these development tools and seeing what I can do.
Books
You might enjoy printed books. If you do then this one would be a strong pick - Flutter for Beginners: An introductory guide to building cross-platform mobile applications with Flutter 2.5 and Dart, 2nd Edition. As is Flutter Projects: A practical, project-based guide to building real-world cross-platform mobile applications and games (both on Amazon UK).

Of course, one does not simply publish an app.
In order to publish an app you have to plan, code, build, test, publish, operate, monitor, manage and update it. This is a familiar pattern these days, its sometimes called 'devops', and it can't be avoided, for if you skip elements of it then updating your app or managing it might become impossible over time. You don't want that! So while this collection of articles is based around Flutter it is also about all the surrounding disciplines that need to be considered as part of development and operating a cross platform mobile application and the inevitable platform behind it.
Flutter tech notes and demos
Build and publish a Flutter-based app for the App Store and Google Play — updated for 2026 with current FlutterFire packages, the latest Firebase Authentication APIs, and notes on what has changed since this series began.
Let's create an authenticated end user.
TL:DR – Authentication is built into Firebase. The heavy lifting is handled for you, so you can stay focused on what makes your app unique. Now, that story is even stronger thanks to the Identity Platform upgrade and a much-improved FlutterFire SDK.
- Details
Build and publish a Flutter-based app for the App Store and Google Play — updated for 2026 with the latest Firebase and Flutter tooling.
Once you have a user signed in, you can open your mobile app to personalisation based on that individual. Google Sign-in remains one of the most frictionless ways to get there, especially for Android users — but the setup steps have evolved, and the tooling has improved considerably.
TL:DR – We now have an authenticated user connected to Google Firebase and its Cloud Firestore database. This instalment extends the app to actually do something with that connection, starting with wiring up Google as a sign-in provider.
- Details
Build and publish a Flutter-based app for the App Store and Google Play — updated here for 2026 to reflect the current state of async Dart and the device_info_plus plugin.
As we dig deeper into sign-in flows, the app needs to query the device it's running on and surface relevant choices in the UI. That means working with asynchronous code — and in 2026, Dart's async story is more capable and better-tooled than ever.
TL:DR – This instalment focuses on asynchronous programming with futures, async, and await, then applies those concepts to retrieving device information inside a Flutter app.
- Details
Read more: Asynchronous programming in Flutter, futures, async, await
Build and publish a Flutter-based app for the App Store and Google Play — updated for 2026 with the latest package versions and platform requirements.
Introducing plugins from pub.dev, and specifically how to add Sign in with Apple to your Flutter app today.
TL:DR – Now that we have authentication connected to Google Firebase and its Cloud Firestore database, let's extend our app to allow Sign in with Apple too. The sign_in_with_apple package has matured considerably since this series began and is now on version 8.0.0, with full support across Android, iOS, macOS, and Web.
- Details
Read more: Sign in with Apple from Pub.Dev Package Manager for Flutter
This is part of a journey to build and publish a Flutter based app for the App Store and Google Play — updated for 2026 with current tooling, workflows, and platform guidance.
At some point in any serious project, source code control stops being optional. If you're working across more than one machine — or simply want a safety net — Git is the answer.
TL:DR – This article covers Git setup and daily use across macOS and Chrome OS in 2026, including what's changed with authentication, tooling, and the Chrome OS Linux environment.
- Details
Read more: Source Code control with Git from Mac to Chromebook
Building a Flutter 💙 Barcode Scanner app in 2026 — the packages worth using today, what to migrate away from, and how to get scanning fast.
The use case is straightforward: scan a collection of barcoded inventory items, persist them to a database, optionally enrich each record with product metadata from a lookup API, then expose that inventory to other services such as a web dashboard. Flutter and Firebase remain an excellent fit for this kind of cross-platform, cloud-connected workflow — and in 2026 the ecosystem around barcode scanning has matured to the point where the old default recommendation no longer holds.
TL;DR — If you are starting a new project today, skip flutter_barcode_scanner and reach straight for mobile_scanner. It is actively maintained, performs better on modern hardware, and supports every Flutter deployment target. The rest of this article explains why, shows you the code, and covers the full stack from scan to cloud.
{ToC}
What's changed in 2026
A few years ago, flutter_barcode_scanner was the obvious first recommendation for any Flutter barcode project. That is no longer true, and it is worth being direct about why before getting into implementation details.
- flutter_barcode_scanner has seen long quiet periods on GitHub. It still installs and runs on Android and iOS, but it does not support Flutter Web, macOS, Windows, or Linux, and it has not kept pace with improvements to the underlying platform ML pipelines.
- mobile_scanner has become the community-preferred package. It uses Google's ML Kit on Android and Apple's Vision framework on iOS — both of which have improved substantially in recent years — and it tracks Flutter stable with regular releases. Version 5.x, current as of 2026, introduced a cleaner API and improved Web support via the BarcodeDetector browser API with a polyfill fallback.
- Flutter itself is well into the 3.x series, with stable support for Android, iOS, Web, macOS, Windows, and Linux from a single codebase. Any package that doesn't support all six targets is increasingly a liability for teams that want deployment flexibility.
- Dart 3 is the baseline. Null safety is no longer a talking point — it is simply assumed. Packages that predate it require workarounds that are not worth carrying into new projects.
- iOS 18 and Android 15 are now the dominant platform versions in the field. Camera permission flows have been tightened on both platforms, and the Vision framework on iOS has received meaningful accuracy improvements for low-contrast and damaged barcodes.
The short version: mobile_scanner is where the momentum is. This article focuses on it, while keeping a section on flutter_barcode_scanner for teams maintaining older codebases.
The recommended approach: mobile_scanner
Add mobile_scanner to your pubspec.yaml:
dependencies:
mobile_scanner: ^5.0.0
Run flutter pub get, then drop a scanner widget into your widget tree. A minimal but fully functional implementation looks like this:
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerPage extends StatelessWidget {
const BarcodeScannerPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Scan barcode')),
body: MobileScanner(
onDetect: (capture) {
for (final barcode in capture.barcodes) {
debugPrint('Barcode found: ${barcode.rawValue}');
}
},
),
);
}
}
That is genuinely all you need to get a live scanning overlay running on Android or iOS. The MobileScanner widget handles camera lifecycle, permission state, and the ML pipeline internally. From there you can layer in additional controls as your use case demands.
Torch, zoom, and camera switching
Real-world inventory scanning often happens in warehouses or stockrooms where lighting is poor. mobile_scanner exposes torch control, zoom, and front/rear camera switching through a MobileScannerController:
class BarcodeScannerPage extends StatefulWidget {
const BarcodeScannerPage({super.key});
@override
State<BarcodeScannerPage> createState() => _BarcodeScannerPageState();
}
class _BarcodeScannerPageState extends State<BarcodeScannerPage> {
final MobileScannerController _controller = MobileScannerController(
detectionSpeed: DetectionSpeed.noDuplicates,
facing: CameraFacing.back,
torchEnabled: false,
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan barcode'),
actions: [
IconButton(
icon: const Icon(Icons.flashlight_on),
onPressed: () => _controller.toggleTorch(),
),
IconButton(
icon: const Icon(Icons.flip_camera_ios),
onPressed: () => _controller.switchCamera(),
),
],
),
body: MobileScanner(
controller: _controller,
onDetect: (capture) {
for (final barcode in capture.barcodes) {
debugPrint('Barcode: ${barcode.rawValue}');
}
},
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
The DetectionSpeed.noDuplicates setting is particularly useful for inventory workflows — it prevents the same barcode from firing the callback repeatedly while it stays in frame, so you don't have to debounce scans yourself.
Supported barcode formats
mobile_scanner supports all formats you are likely to encounter in an inventory context: QR Code, EAN-13, EAN-8, UPC-A, UPC-E, Code 39, Code 93, Code 128, ITF, Codabar, Data Matrix, Aztec, and PDF417. You can restrict detection to a subset of formats to improve scan speed if your use case is narrow:
final controller = MobileScannerController(
formats: [BarcodeFormat.ean13, BarcodeFormat.ean8, BarcodeFormat.code128],
);
iOS and Android platform setup
Both platforms require explicit camera permission declarations before the scanner will work.
iOS
Add a camera usage description to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan barcodes.</string>
On iOS 17 and later, the Vision framework's barcode detection has improved noticeably — codes that previously required deliberate slow movement are now caught on the first pass in most lighting conditions. iOS 18 tightened the permission prompt UI, so ensure your usage description is clear and user-facing; vague strings are more likely to prompt user denial.
Android
Add the camera permission to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
On Android 14 and 15, the runtime permission dialog is presented at first camera use. mobile_scanner handles the request automatically, but if you want to prompt the user before opening the scanner — for example, to explain why the camera is needed — use the permission_handler package to check and request the permission explicitly beforehand.
Enriching scan results with a product lookup API
A raw barcode value is only the starting point. For an inventory app, you want to resolve that value to a human-readable product record — name, brand, category, image — without requiring the operator to type anything. Several APIs handle this well in 2026.
- Open Food Facts API — free, open, no API key required for read access. Returns product name, brand, ingredients, nutritional data, and images. The v2 API is stable and well-documented. Ideal for food and grocery inventory.
- Barcode Lookup — broader product category coverage beyond food. Free tier available with rate limits; paid plans for higher-volume use.
- Open Product Data / GS1 — for industrial or retail supply-chain contexts where GS1-registered GTINs are in use, the GS1 registry provides authoritative product data directly from brand owners.
A Dart HTTP call to the Open Food Facts v2 API for a scanned EAN-13 confirms the approach works end-to-end:
GET https://world.openfoodfacts.org/api/v2/product/5000157062697.json
→ product_name: "Classic Cream of Mushroom Soup"
→ brands: "Campbell's"
→ quantity: "400g"
In Dart, a minimal lookup function using the http package looks like this:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<Map<String, dynamic>?> lookupBarcode(String barcode) async {
final uri = Uri.parse(
'https://world.openfoodfacts.org/api/v2/product/$barcode.json',
);
final response = await http.get(uri);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
if (data['status'] == 1) {
return data['product'] as Map<String, dynamic>;
}
}
return null;
}
Persisting to Firebase
Saving scanned and enriched inventory items to Cloud Firestore gives you real-time sync, offline support, and a backend that any other service — a web dashboard, a reporting tool, a fulfilment system — can read from directly. The cloud_firestore Flutter plugin is mature, fully null-safe, and tracks Flutter stable.
Future<void> saveInventoryItem({
required String barcode,
required Map<String, dynamic> product,
}) async {
await FirebaseFirestore.instance.collection('inventory').add({
'barcode': barcode,
'productName': product['product_name'] ?? 'Unknown',
'brand': product['brands'] ?? 'Unknown',
'quantity': product['quantity'] ?? '',
'scannedAt': FieldValue.serverTimestamp(),
});
}
A Flutter Web dashboard reading from the same Firestore collection in real time closes the loop on the original project requirement — one scan on a mobile device, immediately visible on a browser dashboard elsewhere, with no custom backend to maintain.
For teams that prefer a non-Google backend in 2026, Supabase is a strong alternative. The supabase_flutter plugin is well-maintained and the Supabase free tier is generous for inventory-scale workloads. The integration pattern is essentially the same — replace the Firestore call with a Supabase table insert.
The older approach: flutter_barcode_scanner
For teams maintaining an existing codebase built on flutter_barcode_scanner, here is the context you need.
The package still installs cleanly on current Flutter toolchains and still works for Android and iOS targets. If your project is pinned to it and you need to ship quickly, it will not block you. The integration involves forking the repository as a precaution against upstream stalls — which, as noted, have occurred — and referencing your fork in pubspec.yaml:
dependencies:
flutter_barcode_scanner:
git:
url: https://github.com/your-username/flutter_barcode_scanner.git
ref: main
The scanning overlay and result callback work as documented. For a simple Android or iOS project with no Web or desktop requirement, it remains functional. But the migration path to mobile_scanner is not onerous — the API surface is similar enough that a motivated developer can make the switch in an afternoon — and the long-term maintenance picture strongly favours moving.
The console output for a scanned EAN-13 barcode is the same either way:
I/flutter (31686): 5000157062697
Choosing between mobile_scanner and google_mlkit_barcode_scanning
A third option worth knowing about is google_mlkit_barcode_scanning — Google's ML Kit barcode plugin for Flutter, maintained by the Google ML Kit team. It gives you direct access to the ML Kit barcode scanning pipeline without the camera widget layer that mobile_scanner includes.
Use mobile_scanner when you want a complete, ready-to-use scanning widget with camera management included — which covers the majority of inventory app use cases. Use google_mlkit_barcode_scanning when you need to process images from a source other than the device camera — for example, scanning barcodes in photos from the gallery, or processing frames from a custom camera implementation. The two packages are complementary rather than competing.
Summary
mobile_scanner is the right starting point for any new Flutter barcode project in 2026. It is actively maintained, performs well on current Android and iOS hardware, supports all six Flutter deployment targets, and requires very little boilerplate to get a working scanner in front of users. The broader stack — Firestore or Supabase for persistence, Open Food Facts or Barcode Lookup for product enrichment, Flutter Web for a real-time dashboard — is mature and well-supported.
flutter_barcode_scanner still runs on Android and iOS and is not an emergency migration if you have an existing project depending on it. But for anything new, it is the wrong starting point, and the case for migrating existing projects grows stronger as Flutter's multi-platform ambitions expand.
Scan, enrich, persist, expose. The workflow is simple. The tools in 2026 are better than they have ever been.
See also:
- mobile_scanner on pub.dev
- flutter_barcode_scanner on pub.dev
- google_mlkit_barcode_scanning on pub.dev
- supabase_flutter on pub.dev
- Open Food Facts API
- Barcode lookup — EAN 5000157062697, Classic Cream of Mushroom Soup
- permission_handler on pub.dev
- Details
Flutter development on a Chromebook
Running Android Studio, ChromeOS, and Android simultaneously on a Chromebook remains one of the more underrated development setups available in 2026. The Android subsystem is not an emulator — it is the full ChromeOS Android implementation — and when it works, it works beautifully. The landscape has shifted considerably since this workflow first became popular, so this guide has been fully updated to reflect the current state of ChromeOS development.
TL:DR – ChromeOS is still a compelling choice for Flutter development, particularly for Android-targeted work. The Google Pixelbook line has been discontinued, and Google is no longer manufacturing its own Chromebook hardware under that brand. The good news is that the ChromeOS ecosystem has matured, and there are strong alternatives from Lenovo, HP, and ASUS at a range of price points. For serious development work, you still want a machine with at least 16GB of RAM, a fast processor, and a generous SSD — cheap Chromebooks remain a poor experience for running Android Studio and the Linux container simultaneously. Do bear in mind that you cannot build for iOS or macOS on a Chromebook; you will need a Mac for those targets.
{ToC}
What's changed in 2026
The most significant shift since this article was first written is the discontinuation of the Google Pixelbook line. Google exited the first-party Chromebook hardware market, meaning the Pixelbook and Pixelbook Go are now only available second-hand. Prices on the used market have fluctuated, and while these machines remain capable, sourcing reliable units is increasingly hit-or-miss. If you already own one in good condition, it is still a perfectly usable development machine. If you are buying fresh, look at current flagship Chromebooks from Lenovo (the IdeaPad 5i Chromebook or ThinkPad C-series), HP (the Dragonfly Pro Chromebook), or ASUS (the Chromebook Plus range), all of which ship with the hardware headroom needed for Flutter development.
Flutter itself has continued to mature rapidly. The Flutter SDK is now well past version 3, with significant improvements to performance, tooling, and multi-platform support. Android Studio has moved through several major releases and is now substantially faster than before. The ChromeOS-specific Flutter documentation on flutter.dev has also improved, though the installation steps below still diverge from the official guidance in a few important ways — so follow this guide rather than the official ChromeOS page if you run into trouble.
ChromeOS itself has received ongoing updates that make the Linux container more stable and better integrated. File sharing between ChromeOS and Linux is smoother, and the Android subsystem on modern Chromebooks runs Android 13 or later, giving you access to a more current API surface for testing.
Chromebook compatibility
Checking whether your Chromebook is suitable for development is straightforward. Open ChromeOS Settings and scroll toward the bottom. If you see an Apps section with Google Play Store settings, and a Developer section where Linux can be enabled, you are good to go — provided you have sufficient disk space (64GB or more is strongly recommended). If neither option appears and cannot be enabled, the device is not suitable for this workflow and it is worth upgrading.


Recommended hardware in 2026
For new purchases, prioritise machines in the Chromebook Plus tier, which Google introduced as a quality baseline. These devices are certified to meet minimum specs that make them genuinely usable for development. Look for:
- Processor: Intel Core i5 or i7 (12th generation or later), or an AMD Ryzen 5 or 7 equivalent. Avoid ARM-only devices if you want the smoothest Android Studio experience.
- RAM: 16GB minimum. 8GB is workable but you will feel the constraint when Android Studio, the Linux container, and ChromeOS are all competing for memory.
- Storage: 256GB SSD or larger. The Linux container, Android SDK, Flutter SDK, and your projects will consume space quickly.
- Used market: If budget is a priority, a second-hand Pixelbook Go (i7, 16GB) remains a capable machine if you can find one in good condition, but verify its ChromeOS auto-update expiry date before buying — devices past their AUE date no longer receive security updates.
Installing Flutter on a Chromebook with Android Studio

This is more involved than it should be, because the instructions on the Flutter website do not fully reflect the reality of working within ChromeOS. The steps below are tested and work reliably.
- Enable Linux on your Chromebook. When prompted, allocate at least 20GB to the Linux container — more if your storage allows.
- Once Linux is running, update the system: $ sudo apt update && sudo apt upgrade.
- Download the latest stable Flutter SDK from the Flutter release archive. Because the Downloads folder is not directly visible to Linux, use the ChromeOS Files app to move the downloaded archive from Downloads into Linux files.
- Extract the SDK: $ tar xvf flutter_linux_<version>-stable.tar.xz — substitute
<version>with the filename you downloaded. - Move the
flutterfolder to a suitable location: $ sudo mv flutter /usr/local/bin. - Add Flutter to your PATH: $ echo 'export PATH="/usr/local/bin/flutter/bin:$PATH"' >> ~/.bash_profile. Restart the terminal to pick up the change.
- Run $ flutter doctor and address any flagged issues. If this Chromebook is dedicated to Android work, you can suppress other targets: $ flutter config --no-enable-linux-desktop and $ flutter config --no-enable-web.
- A healthy Flutter doctor output should look something like this:
At some point during setup you will be prompted to allow a debug connection from the Android subsystem. Accept this, and your Chromebook's Android environment will appear as a connected device.$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, on Debian GNU/Linux 12 (bookworm), locale en_US.UTF-8) [✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [✓] Android Studio (version 2024.x) [✓] Connected device (1 available) [✓] Network resources • No issues found!$ flutter devices Found 1 connected device: <Your Chromebook model> (mobile) • emulator-5554 • android-x64 • Android 13 (API 33) - Verify that the required shell tools are present: $ which bash file mkdir rm which.
- Download and install Android Studio from the official site. The site will detect ChromeOS and offer the correct
.debpackage. Current releases are in the Ladybug or later series — accept whatever the latest stable version is at the time you install. - Open the Files app, locate the
.debpackage in Downloads, right-click it, and select Install with Linux. Confirm the installation and wait — this can take several minutes.
Chromebook DEB package file — Install with Linux - Work through the Android Studio Setup Wizard. Ensure that the following SDK components are installed:
Android SDK Platform (API 35.0.0 or later),Android SDK Command-line Tools,Android SDK Build-Tools, andAndroid SDK Platform-Tools. The Command-line Tools component is frequently missed and will causeflutter doctorto complain if absent.
Ensure the Command Line Tools component is checked — it is easy to miss - Once installation is complete, launch Android Studio from the ChromeOS Launcher or directly from the terminal: $ /opt/android-studio/bin/studio.sh.
- Choose New Flutter Project, point the Flutter SDK field at
/usr/local/bin/flutter, and let Android Studio configure itself. Once the project is created, press Run. The app will build, install into the Android subsystem, and launch — just as shown in the screenshot at the top of this section.
A note on the Flutter plugin for Android Studio
In recent Android Studio releases, the Flutter and Dart plugins are managed through the JetBrains Marketplace inside Android Studio's settings rather than being bundled by default. If Android Studio does not offer you a Flutter project template on first launch, navigate to Settings → Plugins → Marketplace, search for Flutter, and install it. The Dart plugin will be installed automatically as a dependency. Restart Android Studio and the Flutter project options will appear.
It is a revelation to have Android Studio, ChromeOS and Android all running simultaneously on a Chromebook. The Android subsystem is not an emulator — it is the full ChromeOS Android implementation.
Resources
Flutter for ChromeOS — official installation guide (flutter.dev)
Flutter stable release archive
Android Studio — latest release
Enabling Linux on your Chromebook (Google Support)
Chromebook Plus — Google's current quality tier for Chromebook hardware
- Details
Cloud Firestore is a flexible, scalable NoSQL database for mobile, web, and server development. As of 2026, it remains the recommended database back end for FlutterFlow projects, and Google has continued to invest heavily in it — most notably with the arrival of an Enterprise edition in Native mode and support for multiple databases within a single project. If you are building a mobile app that needs a database back end, Cloud Firestore is still the clearest path to production.
Cloud Firestore is a NoSQL database, which means you don't define its entire structure in tables ahead of time and you don't use SQL statements to query it. Instead, you store data in collections, which contain documents, which contain fields of various types mapped to values. The supported data types are: Array, Boolean, Bytes, Date and time, Floating-point number, Geographical point, Integer, Map, Null, Reference, and Text string.
TL:DR – There is a fair amount of setup involved. You will need to configure Cloud Firestore for the Web, and to run the Firebase Local Emulator Suite you will need a Java Runtime Environment (JRE) — the quickest route on a modern Mac is via Homebrew.
- Details
Customising the Firestore Quickstart for 2026
Adapting the FriendlyEats quickstart and connecting it to the Firebase Local Emulator Suite
In this walkthrough, we take the Firestore quickstart — part of the actively maintained quickstart-js repository on GitHub — and reshape it into a working web admin app for a FlutterFlow-based mobile app backed by Firebase. As of 2026, the quickstart-js repository has been migrated to TypeScript and continues to receive regular updates, so some of what you find there will look a little different from older tutorials.
TL:DR – This article focuses on the practical modifications needed to turn the FriendlyEats quickstart into a custom admin app for a Firestore database. It also covers connecting that app to the Firebase Local Emulator Suite so you can experiment safely without touching production data. In the end I didn't pursue this particular approach beyond the prototype stage — but the process taught me a great deal, and the steps here are still valid and useful in 2026.
- Details
Starting from a sample in Flutter — 2026 edition
Back when I first wrote this, spinning up a Flutter project with Firebase integration required a fair amount of manual configuration across every platform. In 2026, the tooling has matured considerably — Flutter 3.44 is the current stable release, the FlutterFire CLI handles most of the Firebase wiring automatically, and the flutterfire configure command has replaced the old hand-editing of platform config files for the majority of cases. That said, the core workflow of starting from a Flutter sample and layering Firebase on top remains a solid approach, so this article is worth revisiting with fresh eyes.
TL:DR – This article walks through iterating towards a Flutter app that bootstraps Firebase data for a companion FlutterFlow project. The original thinking was to keep a simple Flutter app for back-end admin work and use FlutterFlow for the prettier end-user front end. In practice, that split didn't hold up — it's easier and more maintainable to build admin functionality as role-based or user-elevated capabilities directly inside FlutterFlow. All the logic lives in one place, and you get much better adherence to DRY principles. The setup steps below are still useful if you want a standalone Flutter admin app, but go in with eyes open.
- Details
Export compliance and the App Store
When you submit an app for publication in the App Store you are uploading your app to servers located in the United States. Downloads of your app outside the United States and Canada are treated as exports from the United States — and as of 2026, U.S. Department of Commerce encryption export administration regulations (EAR) continue to apply in full. If your app uses encryption in any form, you need to understand your obligations before you ship.
TL:DR – Apps distributed via the App Store are U.S. exports. If they use encryption, they need either an export licence or a qualifying exemption. Most apps qualify for an exemption, but you still have to file an annual self-classification report with the U.S. government.
- Details
Getting the latest video from a particular YouTube channel into a FlutterFlow app sounds straightforward — until you realise how many layers sit between a YouTube channel and a working video URL in your Flutter widget. This article walks through the full solution using YouTube's RSS feed, a Dart custom action, and FlutterFlow's YouTube Player widget. It was first written in 2022 and has been fully refreshed for 2026, reflecting current FlutterFlow conventions, updated Dart package versions, and the modern FlutterFlow custom code workflow.
The detail page in question is, on the face of it, very simple. Part of it comprises a container populated with data from Google Firebase, making the elements engaging and useful. You can tap the YouTube video to play it, and tap buttons to invoke the relevant engagement methods for the entity whose information has been pulled from Firebase. The YouTube Player component in FlutterFlow is excellent — all it needs is the URL of the video to work with.
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/X, YouTube, Facebook and phone call.
Screenshot from FlutterFlow designer
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 populated by data from Firebase.
Screenshot from FlutterFlow designer
In the end this became a journey into FlutterFlow custom actions, pubspec dependencies, Dart programming and XML parsing. It took longer than expected because each piece needed to be learned sequentially — but once it clicked, the solution works reliably and updates automatically every time the page loads.
{ToC}
What's changed in 2026
Since this article was first written, a few things have shifted that are worth knowing before you dive in.
- FlutterFlow's custom code tooling has matured significantly. The in-browser code editor now offers better inline error reporting and dependency resolution hints. You can also use the FlutterFlow VS Code extension to write and test custom actions locally with full IDE support, which is a substantial improvement over the earlier workflow of downloading the project and debugging blind.
- The
xmlDart package has moved on. Version6.xis now current and is compatible with modern FlutterFlow projects. The^5.3.0ceiling mentioned in the original article no longer applies — you should targetxml: ^6.5.0in your pubspec dependencies. - FlutterFlow's YouTube Player widget remains a first-class Base Element, and the approach described here still works exactly as intended. The widget API has not changed in any breaking way.
- YouTube's RSS feed is still live. Despite years of speculation that Google would deprecate it, the feed endpoint at
https://www.youtube.com/feeds/videos.xml?channel_id=remains available and undocumented-but-functional in 2026. It continues to be the cleanest way to retrieve a channel's latest video without needing an API key or quota management. - Twitter is now X. References to a Twitter button in the UI are updated accordingly, though the underlying approach is identical.
- VSCodium remains a solid choice for local Flutter debugging, and the FlutterFlow VS Code extension now makes it easier to keep your local codebase in sync with the FlutterFlow project without a full manual download each time.
YouTube RSS feeds
YouTube has made an RSS feed available since the platform launched. It is not prominently advertised — YouTube would clearly prefer you to use their website and player — but it has proven remarkably durable. In 2026 it remains the simplest way to programmatically retrieve a channel's latest video without registering for the YouTube Data API, managing OAuth credentials, or worrying about quota limits.
YouTube Channel ID
To use the RSS feed you need the YouTube Channel ID. This is sometimes visible directly in the channel URL — for example, https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw — where the channel ID is the final segment: UCuAXFkgsw1L7xaCfnd5JJOw.
Vanity URLs and individual video links don't expose the channel ID directly. A small shell script (linked at the bottom of this page) can resolve the channel ID from any YouTube video share link. Share a video, paste the link as a parameter, and you get back the full RSS feed URL.
% ./youtube-rss.sh https://youtu.be/dQw4w9WgXcQ
https://www.youtube.com/feeds/videos.xml?channel_id=UCuAXFkgsw1L7xaCfnd5JJOw
YouTube RSS feed structure
The RSS feed URL follows a consistent pattern with channel_id as the query parameter. The feed returns structured XML from which you can extract the unique video identifier for every video in the channel's recent history.
Inside each <entry> block you will find the video identifier in the <yt:videoId> element — for example <yt:videoId>LLFhKaqnWwk</yt:videoId>. The entries appear in reverse chronological order, so the first entry is always the most recent video. Here is an excerpt:
<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>
</author>
</entry>
The YouTube video player in FlutterFlow
The FlutterFlow YouTubePlayer widget is a Base Element — a first-party, maintained component that ships as part of FlutterFlow itself. You can find it in the Base Elements section of the Widget Panel, or add it directly from the widget tree.

Beyond the standard FlutterFlow controls, the YouTube Player widget lets you set the video URL, whether to loop, whether to mute, whether to show YouTube's native controls, and whether to allow full screen. In this implementation the URL field uses a Combine Text variable as its input. The URL returned from the custom action is not quite in the format the control expects, and wrapping it in a Combine Text expression resolves that cleanly.

The Combine Text has a single value — the variable ytVid — which is produced by the custom action described below.
FlutterFlow Custom Action to populate the YouTube URL
The goal is for the page to always show the channel's most recent video without any manual intervention. Even with a modest number of detail pages, keeping video links current by hand would become unmanageable quickly. Running this logic on the device at page load time is fast, free (no Firebase function invocation cost), and requires no server-side infrastructure. The only requirement is an active internet connection, which is a reasonable assumption for a mobile app in 2026.
The key insight: run the action on the calling page, not the destination page
The custom action that fetches the latest video is not placed on the detail page itself. It runs on the calling page, as an On Tap action, immediately before the Navigate To action that opens the detail page. This keeps the detail page lean and fast to render.
On Tap Action

The action takes a single argument — idChannel, a string pulled from the Firebase document representing the channel ID for this entity — and returns a single Action Output variable, ytVid, containing the latest video URL ready for the YouTube Player on the next screen.
Page parameters
The Navigate To action passes page parameters to the destination, including ytVid for the YouTube Player to consume.

Why prototype the custom action as a standalone Dart app first?
FlutterFlow is a low-code platform, not a no-code one — and that distinction matters. You have the full power of Dart and the pub.dev ecosystem available through custom actions and custom widgets. The trade-off is that when something goes wrong, the feedback loop inside FlutterFlow's browser-based editor can be slower than working in a local IDE.
For non-trivial custom actions, it pays to validate your logic as a plain Dart command-line app first. This removes all Flutter and FlutterFlow moving parts while you work through the logic, and gives you a tight, fast iteration loop. Once the Dart app works correctly, porting it to a FlutterFlow custom action is a small mechanical step.
Dart: the ytxml prototype app
ytxml is a minimal command-line Dart application. Given a YouTube Channel ID, it fetches the RSS feed and returns the URL of the most recent 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: ^6.5.0
ytxml.dart
//
// Copyright 2022-2026 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 main Future and if statement above, and uncomment
// the line below, to use this code in a FlutterFlow custom action.
// Future<String> latestVideo(String idChannel) async {
var idString = idChannel.toString();
print(idString); // Verify the parameter. 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); // Verify the returned video link. Remove for production.
return (firstVideo);
}
This app is asynchronous, using a Dart Future. The xml package parses the RSS feed for the given channel ID, extracts all video identifiers into a list, and uses Dart's .first property to grab the first entry — which is consistently the most recent video. That value is returned as the firstVideo string, either printed to the console during prototyping or handed back to FlutterFlow as the custom action's return value.
One note on logging: Dart's print() function works fine in a standalone command-line app and in FlutterFlow custom actions during development. For production Flutter apps, prefer debugPrint() (which requires import 'package:flutter/foundation.dart') or a dedicated logging package. The flutter/foundation.dart import is not available in a pure Dart command-line context, which is another reason to keep the prototype and the FlutterFlow action as separate, clearly commented variants of the same code.
FlutterFlow Custom Action
Once the standalone Dart app produces the correct output, porting it to FlutterFlow is straightforward. Paste the working code into a new custom action, then make the following adjustments:
- Comment out the
Future<String> main(String idChannel)declaration and the empty-checkifblock beneath it. - Uncomment the
Future<String> latestVideo(String idChannel)declaration. - Set the FlutterFlow Action name to
latestVideo. - Set the return type to String.
- Define an argument
idChannelof type String. - Add the pubspec dependencies:
xml: ^6.5.0. Theconvertpackage is part of the Dart SDK and does not need a separate entry in modern FlutterFlow projects.

Compile the action inside FlutterFlow. Any dependency warnings in the editor can generally be ignored at this stage — FlutterFlow's browser-based linter does not fully resolve transitive dependencies, but the build process does. Clear all genuine code errors, then run the app. In most cases, if the standalone Dart prototype worked, the FlutterFlow action will work first time.
Debugging with VS Code and the FlutterFlow extension
Since this article was first written, the FlutterFlow VS Code extension has become the recommended way to work with custom code locally. It keeps your local project in sync with your FlutterFlow canvas without requiring a manual project download each time, and gives you full IDE debugging support — breakpoints, variable inspection, and the full Dart DevTools suite.
If you prefer a fully open-source toolchain, VSCodium — the community-maintained, freely-licensed binary distribution of VS Code — works equally well and supports the same Flutter and Dart extensions.

I/flutter) shows the idChannel passed to the custom action and the latestVideo URL returned to FlutterFlow. The Dart DevTools panel on the right shows the same values as stdout entries.Whether you use VS Code, VSCodium, or Android Studio, attaching to a real device over USB and filtering the log output by I/flutter is the fastest way to confirm that your custom action is receiving the right input and returning the right output before you remove the print() statements for production.
See also
A script to get the RSS feed for a YouTube page by Timothy J. Luoma.
The FlutterFlow YouTubePlayer widget documentation.
Dart.Dev — Get started: Command-line and server apps
Dart.Dev — Asynchronous programming: futures, async, await
xml package on pub.dev
VSCodium and Flutter on this website
- Details