Don't miss ourDecember livestream, with updates on Flutter and live Q&A!
Flutter for Android developers
Learn how to apply Android developer knowledge when building Flutter apps.
This document is meant for Android developers looking to apply their existing Android knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Android framework then you can use this document as a jump start to Flutter development.
Android has two native user interface systems, Views (XML based) and Jetpack Compose. Some fundamentals are shared so this document will provide value no matter what. However, if you are coming from Jetpack Compose, check outFlutter for Jetpack Compose devs for detailed information about Jetpack Compose and how samples match up to Flutter examples.
To integrate Flutter code into your Android app, seeAdd Flutter to existing app.
Your Android knowledge and skill set are highly valuable when building with Flutter, because Flutter relies on the mobile operating system for numerous capabilities and configurations. Flutter is a new way to build UIs for mobile, but it has a plugin system to communicate with Android (and iOS) for non-UI tasks. If you're an expert with Android, you don't have to relearn everything to use Flutter.
This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.
Views
#What is the equivalent of a View in Flutter?
#How is react-style, ordeclarative, programming different than the traditional imperative style? For a comparison, seeIntroduction to declarative UI.
In Android, theView is the foundation of everything that shows up on the screen. Buttons, toolbars, and inputs, everything is a View. In Flutter, the rough equivalent to aView is aWidget. Widgets don't map exactly to Android views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI".
However, these have a few differences to aView. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, an Android view is drawn once and does not redraw untilinvalidate is called.
Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood.
Flutter includes theMaterial Components library. These are widgets that implement theMaterial Design guidelines. Material Design is a flexible design systemoptimized for all platforms, including iOS.
But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use theCupertino widgets to produce an interface that looks likeApple's iOS design language.
How do I update widgets?
# In Android, you update your views by directly mutating them. However, in Flutter,Widgets are immutable and are not updated directly, instead you have to work with the widget's state.
This is where the concept ofStateful andStateless widgets comes from. AStatelessWidget is just what it sounds like—a widget with no state information.
StatelessWidgets are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object.
For example, in Android, this is similar to placing anImageView with your logo. The logo is not going to change during runtime, so use aStatelessWidget in Flutter.
If you want to dynamically change the UI based on data received after making an HTTP call or user interaction then you have to work withStatefulWidget and tell the Flutter framework that the widget'sState has been updated so it can update that widget.
The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is theStatefulWidget has aState object that stores state data across frames and restores it.
If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it's stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn't itself react to change.
The following example shows how to use aStatelessWidget. A commonStatelessWidget is theText widget. If you look at the implementation of theText widget you'll find that it subclassesStatelessWidget.
Text('I like Flutter!',style:TextStyle(fontWeight:FontWeight.bold),); As you can see, theText Widget has no state information associated with it, it renders what is passed in its constructors and nothing more.
But, what if you want to make "I Like Flutter" change dynamically, for example when clicking aFloatingActionButton?
To achieve this, wrap theText widget in aStatefulWidget and update it when the user clicks the button.
For example:
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{// Default placeholder text.StringtextToShow='I Like Flutter';void_updateText(){setState((){// Update the text.textToShow='Flutter is Awesome!';});}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:Text(textToShow)),floatingActionButton:FloatingActionButton(onPressed:_updateText,tooltip:'Update Text',child:constIcon(Icons.update),),);}}How do I lay out my widgets? Where is my XML layout file?
#In Android, you write layouts in XML, but in Flutter you write your layouts with a widget tree.
The following example shows how to display a simple widget with padding:
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:ElevatedButton(style:ElevatedButton.styleFrom(padding:constEdgeInsets.only(left:20,right:30),),onPressed:(){},child:constText('Hello'),),),);}You can view some of the layouts that Flutter has to offer in thewidget catalog.
How do I add or remove a component from my layout?
# In Android, you calladdChild() orremoveChild() on a parent to dynamically add or remove child views. In Flutter, because widgets are immutable there is no direct equivalent toaddChild(). Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag.
For example, here is how you can toggle between two widgets when you click on aFloatingActionButton:
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{// Default value for toggle.booltoggle=true;void_toggle(){setState((){toggle=!toggle;});}Widget_getToggleChild(){if(toggle){returnconstText('Toggle One');}else{returnElevatedButton(onPressed:(){},child:constText('Toggle Two'));}}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:_getToggleChild()),floatingActionButton:FloatingActionButton(onPressed:_toggle,tooltip:'Update Text',child:constIcon(Icons.update),),);}}How do I animate a widget?
# In Android, you either create animations using XML, or call theanimate() method on a view. In Flutter, animate widgets using the animation library by wrapping widgets inside an animated widget.
In Flutter, use anAnimationController which is anAnimation<double> that can pause, seek, stop and reverse the animation. It requires aTicker that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or moreAnimations and attach them to the controller.
For example, you might useCurvedAnimation to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and theCurvedAnimation computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition.
When building the widget tree you assign theAnimation to an animated property of a widget, such as the opacity of aFadeTransition, and tell the controller to start the animation.
The following example shows how to write aFadeTransition that fades the widget into a logo when you press theFloatingActionButton:
import'package:flutter/material.dart';voidmain(){runApp(constFadeAppTest());}classFadeAppTestextendsStatelessWidget{constFadeAppTest({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Fade Demo',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constMyFadeTest(title:'Fade Demo'),);}}classMyFadeTestextendsStatefulWidget{constMyFadeTest({super.key,requiredthis.title});finalStringtitle;@overrideState<MyFadeTest>createState()=>_MyFadeTest();}class_MyFadeTestextendsState<MyFadeTest>withTickerProviderStateMixin{lateAnimationControllercontroller;lateCurvedAnimationcurve;@overridevoidinitState(){super.initState();controller=AnimationController(duration:constDuration(milliseconds:2000),vsync:this,);curve=CurvedAnimation(parent:controller,curve:Curves.easeIn);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text(widget.title)),body:Center(child:FadeTransition(opacity:curve,child:constFlutterLogo(size:100),),),floatingActionButton:FloatingActionButton(tooltip:'Fade',onPressed:(){controller.forward();},child:constIcon(Icons.brush),),);}}For more information, seeAnimation & Motion widgets, theAnimations tutorial, and theAnimations overview.
How do I use a Canvas to draw/paint?
# In Android, you would use theCanvas andDrawable to draw images and shapes to the screen. Flutter has a similarCanvas API as well, since it's based on the same low-level rendering engine, Skia. As a result, painting to a canvas in Flutter is a very familiar task for Android developers.
Flutter has two classes that help you draw to the canvas:CustomPaint andCustomPainter, the latter of which implements your algorithm to draw to the canvas.
To learn how to implement a signature painter in Flutter, see Collin's answer onCustom Paint.
import'package:flutter/material.dart';voidmain()=>runApp(constMaterialApp(home:DemoApp()));classDemoAppextendsStatelessWidget{constDemoApp({super.key});@overrideWidgetbuild(BuildContextcontext)=>constScaffold(body:Signature());}classSignatureextendsStatefulWidget{constSignature({super.key});@overrideSignatureStatecreateState()=>SignatureState();}classSignatureStateextendsState<Signature>{List<Offset?>_points=<Offset>[];@overrideWidgetbuild(BuildContextcontext){returnGestureDetector(onPanUpdate:(details){setState((){RenderBox?referenceBox=context.findRenderObject()asRenderBox;OffsetlocalPosition=referenceBox.globalToLocal(details.globalPosition,);_points=List.from(_points)..add(localPosition);});},onPanEnd:(details)=>_points.add(null),child:CustomPaint(painter:SignaturePainter(_points),size:Size.infinite,),);}}classSignaturePainterextendsCustomPainter{SignaturePainter(this.points);finalList<Offset?>points;@overridevoidpaint(Canvascanvas,Sizesize){varpaint=Paint()..color=Colors.black..strokeCap=StrokeCap.round..strokeWidth=5;for(inti=0;i<points.length-1;i++){if(points[i]!=null&&points[i+1]!=null){canvas.drawLine(points[i]!,points[i+1]!,paint);}}}@overrideboolshouldRepaint(SignaturePainteroldDelegate)=>oldDelegate.points!=points;}How do I build custom widgets?
# In Android, you typically subclassView, or use a pre-existing view, to override and implement methods that achieve the desired behavior.
In Flutter, build a custom widget bycomposing smaller widgets (instead of extending them). It is somewhat similar to implementing a customViewGroup in Android, where all the building blocks are already existing, but you provide a different behavior—for example, custom layout logic.
For example, how do you build aCustomButton that takes a label in the constructor? Create a CustomButton that composes aElevatedButton with a label, rather than by extendingElevatedButton:
classCustomButtonextendsStatelessWidget{finalStringlabel;constCustomButton(this.label,{super.key});@overrideWidgetbuild(BuildContextcontext){returnElevatedButton(onPressed:(){},child:Text(label));}}Then useCustomButton, just as you'd use any other Flutter widget:
@overrideWidgetbuild(BuildContextcontext){returnconstCenter(child:CustomButton('Hello'));}Intents
#What is the equivalent of an Intent in Flutter?
# In Android, there are two main use cases forIntents: navigating between Activities, and communicating with components. Flutter, on the other hand, does not have the concept of intents, although you can still start intents through native integrations (usinga plugin).
Flutter doesn't really have a direct equivalent to activities and fragments; rather, in Flutter you navigate between screens, using aNavigator andRoutes, all within the sameActivity.
ARoute is an abstraction for a "screen" or "page" of an app, and aNavigator is a widget that manages routes. A route roughly maps to anActivity, but it does not carry the same meaning. A navigator can push and pop routes to move from screen to screen. Navigators work like a stack on which you canpush() new routes you want to navigate to, and from which you canpop() routes when you want to "go back".
In Android, you declare your activities inside the app'sAndroidManifest.xml.
In Flutter, you have a couple options to navigate between pages:
- Specify a
Mapof route names. (usingMaterialApp) - Directly navigate to a route. (using
WidgetsApp)
The following example builds a Map.
voidmain(){runApp(MaterialApp(home:constMyAppHome(),// Becomes the route named '/'.routes:<String,WidgetBuilder>{'/a':(context)=>constMyPage(title:'page A'),'/b':(context)=>constMyPage(title:'page B'),'/c':(context)=>constMyPage(title:'page C'),},),);}Navigate to a route bypushing its name to theNavigator.
Navigator.of(context).pushNamed('/b'); The other popular use-case forIntents is to call external components such as a Camera or File picker. For this, you would need to create a native platform integration (or use anexisting plugin).
To learn how to build a native platform integration, seedeveloping packages and plugins.
How do I handle incoming intents from external applications in Flutter?
#Flutter can handle incoming intents from Android by directly talking to the Android layer and requesting the data that was shared.
The following example registers a text share intent filter on the native activity that runs our Flutter code, so other apps can share text with our Flutter app.
The basic flow implies that we first handle the shared text data on the Android native side (in ourActivity), and then wait until Flutter requests for the data to provide it using aMethodChannel.
First, register the intent filter for all intents inAndroidManifest.xml:
<activityandroid:name=".MainActivity"android:launchMode="singleTop"android:theme="@style/LaunchTheme"android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"android:hardwareAccelerated="true"android:windowSoftInputMode="adjustResize"><!-- ...--><intent-filter><action android:name="android.intent.action.SEND" /><category android:name="android.intent.category.DEFAULT" /><data android:mimeType="text/plain" /></intent-filter></activity> Then inMainActivity, handle the intent, extract the text that was shared from the intent, and hold onto it. When Flutter is ready to process, it requests the data using a platform channel, and it's sent across from the native side:
packagecom.example.shared;importandroid.content.Intent;importandroid.os.Bundle;importandroidx.annotation.NonNull;importio.flutter.plugin.common.MethodChannel;importio.flutter.embedding.android.FlutterActivity;importio.flutter.embedding.engine.FlutterEngine;importio.flutter.plugins.GeneratedPluginRegistrant;publicclassMainActivityextendsFlutterActivity{privateStringsharedText;privatestaticfinalStringCHANNEL="app.channel.shared.data";@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);Intentintent=getIntent();Stringaction=intent.getAction();Stringtype=intent.getType();if(Intent.ACTION_SEND.equals(action)&&type!=null){if("text/plain".equals(type)){handleSendText(intent);// Handle text being sent}}}@OverridepublicvoidconfigureFlutterEngine(@NonNullFlutterEngineflutterEngine){GeneratedPluginRegistrant.registerWith(flutterEngine);newMethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),CHANNEL).setMethodCallHandler((call,result)->{if(call.method.contentEquals("getSharedText")){result.success(sharedText);sharedText=null;}});}voidhandleSendText(Intentintent){sharedText=intent.getStringExtra(Intent.EXTRA_TEXT);}}Finally, request the data from the Flutter side when the widget is rendered:
import'package:flutter/material.dart';import'package:flutter/services.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample Shared App Handler',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{staticconstplatform=MethodChannel('app.channel.shared.data');StringdataShared='No data';@overridevoidinitState(){super.initState();getSharedText();}@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Center(child:Text(dataShared)));}Future<void>getSharedText()async{varsharedData=awaitplatform.invokeMethod('getSharedText');if(sharedData!=null){setState((){dataShared=sharedDataasString;});}}}What is the equivalent of startActivityForResult()?
# TheNavigator class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done byawaiting on theFuture returned bypush().
For example, to start a location route that lets the user select their location, you could do the following:
Object?coordinates=awaitNavigator.of(context).pushNamed('/location'); And then, inside your location route, once the user has selected their location you canpop the stack with the result:
Navigator.of(context).pop({'lat':43.821757,'long':-79.226392});Async UI
#What is the equivalent of runOnUiThread() in Flutter?
# Dart has a single-threaded execution model, with support forIsolates (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn anIsolate, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to Android's mainLooper—that is, theLooper that is attached to the main thread.
Dart's single-threaded model doesn't mean you need to run everything as a blocking operation that causes the UI to freeze. Unlike Android, which requires you to keep the main thread free at all times, in Flutter, use the asynchronous facilities that the Dart language provides, such asasync/await, to perform asynchronous work. You might be familiar with theasync/await paradigm if you've used it in C#, Javascript, or if you have used Kotlin's coroutines.
For example, you can run network code without causing the UI to hang by usingasync/await and letting Dart do the heavy lifting:
Future<void>loadData()async{finaldataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalresponse=awaithttp.get(dataURL);setState((){widgets=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});} Once theawaited network call is done, update the UI by callingsetState(), which triggers a rebuild of the widget subtree and updates the data.
The following example loads data asynchronously and displays it in aListView:
import'dart:convert';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>widgets=[];@overridevoidinitState(){super.initState();loadData();}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView.builder(itemCount:widgets.length,itemBuilder:(context,position){returngetRow(position);},),);}WidgetgetRow(inti){returnPadding(padding:constEdgeInsets.all(10),child:Text("Row${widgets[i]["title"]}"),);}Future<void>loadData()async{finaldataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalresponse=awaithttp.get(dataURL);setState((){widgets=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}}Refer to the next section for more information on doing work in the background, and how Flutter differs from Android.
How do you move work to a background thread?
# In Android, when you want to access a network resource you would typically move to a background thread and do the work, as to not block the main thread, and avoid ANRs. For example, you might be using anAsyncTask, aLiveData, anIntentService, aJobScheduler job, or an RxJava pipeline with a scheduler that works on background threads.
Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely useasync/await and you're all set. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to anIsolate to avoid blocking the event loop, like you would keepany sort of work out of the main thread in Android.
For I/O-bound work, declare the function as anasync function, andawait on long-running tasks inside the function:
Future<void>loadData()async{finaldataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalresponse=awaithttp.get(dataURL);setState((){widgets=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}This is how you would typically do network or database calls, which are both I/O operations.
On Android, when you extendAsyncTask, you typically override 3 methods,onPreExecute(),doInBackground() andonPostExecute(). There is no equivalent in Flutter, since youawait on a long-running function, and Dart's event loop takes care of the rest.
However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, useIsolates to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.
Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by callingsetState(). Unlike Android threads, Isolates are true to their name, and cannot share memory (in the form of static fields, for example).
The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.
Future<void>loadData()async{ReceivePortreceivePort=ReceivePort();awaitIsolate.spawn(dataLoader,receivePort.sendPort);// The 'echo' isolate sends its SendPort as the first message.SendPortsendPort=awaitreceivePort.firstasSendPort;finalmsg=awaitsendReceive(sendPort,'https://jsonplaceholder.typicode.com/posts',)asList<Object?>;finalposts=msg.cast<Map<String,Object?>>();setState((){widgets=posts;});}// The entry point for the isolate.staticFuture<void>dataLoader(SendPortsendPort)async{// Open the ReceivePort for incoming messages.ReceivePortport=ReceivePort();// Notify any other isolates what port this isolate listens to.sendPort.send(port.sendPort);awaitfor(varmsginport){StringdataUrl=msg[0]asString;SendPortreplyTo=msg[1]asSendPort;http.Responseresponse=awaithttp.get(Uri.parse(dataUrl));// Lots of JSON to parsereplyTo.send(jsonDecode(response.body));}}Future<Object?>sendReceive(SendPortport,Object?msg){ReceivePortresponse=ReceivePort();port.send([msg,response.sendPort]);returnresponse.first;} Here,dataLoader() is theIsolate that runs in its own separate execution thread. In the isolate you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.
You can run the full example below:
import'dart:async';import'dart:convert';import'dart:isolate';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>widgets=[];@overridevoidinitState(){super.initState();loadData();}WidgetgetBody(){boolshowLoadingDialog=widgets.isEmpty;if(showLoadingDialog){returngetProgressDialog();}else{returngetListView();}}WidgetgetProgressDialog(){returnconstCenter(child:CircularProgressIndicator());}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:getBody(),);}ListViewgetListView(){returnListView.builder(itemCount:widgets.length,itemBuilder:(context,position){returngetRow(position);},);}WidgetgetRow(inti){returnPadding(padding:constEdgeInsets.all(10),child:Text("Row${widgets[i]["title"]}"),);}Future<void>loadData()async{ReceivePortreceivePort=ReceivePort();awaitIsolate.spawn(dataLoader,receivePort.sendPort);// The 'echo' isolate sends its SendPort as the first message.SendPortsendPort=awaitreceivePort.firstasSendPort;finalmsg=awaitsendReceive(sendPort,'https://jsonplaceholder.typicode.com/posts',)asList<Object?>;finalposts=msg.cast<Map<String,Object?>>();setState((){widgets=posts;});}// The entry point for the isolate.staticFuture<void>dataLoader(SendPortsendPort)async{// Open the ReceivePort for incoming messages.ReceivePortport=ReceivePort();// Notify any other isolates what port this isolate listens to.sendPort.send(port.sendPort);awaitfor(varmsginport){StringdataUrl=msg[0]asString;SendPortreplyTo=msg[1]asSendPort;http.Responseresponse=awaithttp.get(Uri.parse(dataUrl));// Lots of JSON to parsereplyTo.send(jsonDecode(response.body));}}Future<Object?>sendReceive(SendPortport,Object?msg){ReceivePortresponse=ReceivePort();port.send([msg,response.sendPort]);returnresponse.first;}}What is the equivalent of OkHttp on Flutter?
# Making a network call in Flutter is easy when you use the popularhttp package.
While the http package doesn't have every feature found in OkHttp, it abstracts away much of the networking that you would normally implement yourself, making it a simple way to make network calls.
To add thehttp package as a dependency, runflutter pub add:
flutter pub add http To make a network call, callawait on theasync functionhttp.get():
import'dart:developer'asdeveloper;import'package:http/http.dart'ashttp;Future<void>loadData()async{vardataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');http.Responseresponse=awaithttp.get(dataURL);developer.log(response.body);}How do I show the progress for a long-running task?
# In Android you would typically show aProgressBar view in your UI while executing a long-running task on a background thread.
In Flutter, use aProgressIndicator widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.
In the following example, the build function is separated into three different functions. IfshowLoadingDialog istrue (whenwidgets.isEmpty), then render theProgressIndicator. Otherwise, render theListView with the data returned from a network call.
import'dart:convert';import'package:flutter/material.dart';import'package:http/http.dart'ashttp;voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Map<String,Object?>>widgets=[];@overridevoidinitState(){super.initState();loadData();}WidgetgetBody(){boolshowLoadingDialog=widgets.isEmpty;if(showLoadingDialog){returngetProgressDialog();}else{returngetListView();}}WidgetgetProgressDialog(){returnconstCenter(child:CircularProgressIndicator());}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:getBody(),);}ListViewgetListView(){returnListView.builder(itemCount:widgets.length,itemBuilder:(context,position){returngetRow(position);},);}WidgetgetRow(inti){returnPadding(padding:constEdgeInsets.all(10),child:Text("Row${widgets[i]["title"]}"),);}Future<void>loadData()async{finaldataURL=Uri.parse('https://jsonplaceholder.typicode.com/posts');finalresponse=awaithttp.get(dataURL);setState((){widgets=(jsonDecode(response.body)asList).cast<Map<String,Object?>>();});}}Project structure & resources
#Where do I store my resolution-dependent image files?
# While Android treats resources and assets as distinct items, Flutter apps have only assets. All resources that would live in theres/drawable-* folders on Android, are placed in an assets folder for Flutter.
Flutter follows a simple density-based format like iOS. Assets might be1.0x,2.0x,3.0x, or any other multiplier. Flutter doesn't havedps but there are logical pixels, which are basically the same as device-independent pixels. Flutter'sdevicePixelRatio expresses the ratio of physical pixels in a single logical pixel.
The equivalent to Android's density buckets are:
| Android density qualifier | Flutter pixel ratio |
|---|---|
ldpi | 0.75x |
mdpi | 1.0x |
hdpi | 1.5x |
xhdpi | 2.0x |
xxhdpi | 3.0x |
xxxhdpi | 4.0x |
Assets are located in any arbitrary folder—Flutter has no predefined folder structure. You declare the assets (with location) in thepubspec.yaml file, and Flutter picks them up.
Assets stored in the native asset folder are accessed on the native side using Android'sAssetManager:
valflutterAssetStream=assetManager.open("flutter_assets/assets/my_flutter_asset.png")Flutter can't access native resources or assets.
To add a new image asset calledmy_icon.png to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily calledimages, you would put the base image (1.0x) in theimages folder, and all the other variants in sub-folders called with the appropriate ratio multiplier:
images/my_icon.png // Base: 1.0x imageimages/2.0x/my_icon.png // 2.0x imageimages/3.0x/my_icon.png // 3.0x imageNext, you'll need to declare these images in yourpubspec.yaml file:
assets:-images/my_icon.pngYou can then access your images usingAssetImage:
AssetImage('images/my_icon.png'),or directly in anImage widget:
@overrideWidgetbuild(BuildContextcontext){returnImage.asset('images/my_image.png');}Where do I store strings? How do I handle localization?
# Flutter currently doesn't have a dedicated resources-like system for strings. The best and recommended practice is to hold your strings in a.arb file as key-value pairs For example:
{"@@locale":"en","hello":"Hello {userName}","@hello":{"description":"A message with a single parameter","placeholders":{"userName":{"type":"String","example":"Bob"}}}}Then in your code, you can access your strings as such:
Text(AppLocalizations.of(context)!.hello('John'));SeeInternationalizing Flutter apps for more information on this.
What is the equivalent of a Gradle file? How do I add dependencies?
#In Android, you add dependencies by adding to your Gradle build script. Flutter uses Dart's own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.
While there are Gradle files under theandroid folder in your Flutter project, only use these if you are adding native dependencies needed for per-platform integration. In general, usepubspec.yaml to declare external dependencies to use in Flutter. A good place to find Flutter packages ispub.dev.
Activities and fragments
#What are the equivalent of activities and fragments in Flutter?
# In Android, anActivity represents a single focused thing the user can do. AFragment represents a behavior or a portion of user interface. Fragments are a way to modularize your code, compose sophisticated user interfaces for larger screens, and help scale your application UI. In Flutter, both of these concepts fall under the umbrella ofWidgets.
To learn more about the UI for building Activities and Fragments, see the community-contributed Medium article,Flutter for Android Developers: How to design Activity UI in Flutter.
As mentioned in theIntents section, screens in Flutter are represented byWidgets since everything is a widget in Flutter. Use aNavigator to move between differentRoutes that represent different screens or pages, or perhaps different states or renderings of the same data.
How do I listen to Android activity lifecycle events?
# In Android, you can override methods from theActivity to capture lifecycle methods for the activity itself, or registerActivityLifecycleCallbacks on theApplication. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into theWidgetsBinding observer and listening to thedidChangeAppLifecycleState() change event.
The observable lifecycle events are:
detached— The application is still hosted on a flutter engine but is detached from any host views.inactive— The application is in an inactive state and is not receiving user input.paused— The application is not currently visible to the user, not responding to user input, and running in the background. This is equivalent toonPause()in Android.resumed— The application is visible and responding to user input. This is equivalent toonPostResume()in Android.
For more details on the meaning of these states, see theAppLifecycleStatus documentation.
As you might have noticed, only a small minority of the Activity lifecycle events are available; whileFlutterActivity does capture almost all the activity lifecycle events internally and send them over to the Flutter engine, they're mostly shielded away from you. Flutter takes care of starting and stopping the engine for you, and there is little reason for needing to observe the activity lifecycle on the Flutter side in most cases. If you need to observe the lifecycle to acquire or release any native resources, you should likely be doing it from the native side, at any rate.
Here's an example of how to observe the lifecycle status of the containing activity:
import'package:flutter/widgets.dart';classLifecycleWatcherextendsStatefulWidget{constLifecycleWatcher({super.key});@overrideState<LifecycleWatcher>createState()=>_LifecycleWatcherState();}class_LifecycleWatcherStateextendsState<LifecycleWatcher>withWidgetsBindingObserver{AppLifecycleState?_lastLifecycleState;@overridevoidinitState(){super.initState();WidgetsBinding.instance.addObserver(this);}@overridevoiddispose(){WidgetsBinding.instance.removeObserver(this);super.dispose();}@overridevoiddidChangeAppLifecycleState(AppLifecycleStatestate){setState((){_lastLifecycleState=state;});}@overrideWidgetbuild(BuildContextcontext){if(_lastLifecycleState==null){returnconstText('This widget has not observed any lifecycle changes.',textDirection:TextDirection.ltr,);}returnText('The most recent lifecycle state this widget observed was:$_lastLifecycleState.',textDirection:TextDirection.ltr,);}}voidmain(){runApp(constCenter(child:LifecycleWatcher()));}Layouts
#What is the equivalent of a LinearLayout?
#In Android, a LinearLayout is used to lay your widgets out linearly—either horizontally or vertically. In Flutter, use the Row or Column widgets to achieve the same result.
If you notice the two code samples are identical with the exception of the "Row" and "Column" widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.
@overrideWidgetbuild(BuildContextcontext){returnconstRow(mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[Text('Row One'),Text('Row Two'),Text('Row Three'),Text('Row Four'),],);}@overrideWidgetbuild(BuildContextcontext){returnconstColumn(mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[Text('Column One'),Text('Column Two'),Text('Column Three'),Text('Column Four'),],);}To learn more about building linear layouts, see the community-contributed Medium articleFlutter for Android Developers: How to design LinearLayout in Flutter.
What is the equivalent of a RelativeLayout?
#A RelativeLayout lays your widgets out relative to each other. In Flutter, there are a few ways to achieve the same result.
You can achieve the result of a RelativeLayout by using a combination of Column, Row, and Stack widgets. You can specify rules for the widgets constructors on how the children are laid out relative to the parent.
For a good example of building a RelativeLayout in Flutter, see Collin's answer onStackOverflow.
What is the equivalent of a ScrollView?
#In Android, use a ScrollView to lay out your widgets—if the user's device has a smaller screen than your content, it scrolls.
In Flutter, the easiest way to do this is using the ListView widget. This might seem like overkill coming from Android, but in Flutter a ListView widget is both a ScrollView and an Android ListView.
@overrideWidgetbuild(BuildContextcontext){returnListView(children:const<Widget>[Text('Row One'),Text('Row Two'),Text('Row Three'),Text('Row Four'),],);}How do I handle landscape transitions in Flutter?
#FlutterView handles the config change if AndroidManifest.xml contains:
android:configChanges="orientation|screenSize"Gesture detection and touch event handling
#How do I add an onClick listener to a widget in Flutter?
#In Android, you can attach onClick to views such as button by calling the method 'setOnClickListener'.
In Flutter there are two ways of adding touch listeners:
- If the Widget supports event detection, pass a function to it and handle it in the function. For example, the ElevatedButton has an
onPressedparameter:
@overrideWidgetbuild(BuildContextcontext){returnElevatedButton(onPressed:(){developer.log('click');},child:constText('Button'),);}- If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to the
onTapparameter.
classSampleTapAppextendsStatelessWidget{constSampleTapApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Center(child:GestureDetector(onTap:(){developer.log('tap');},child:constFlutterLogo(size:200),),),);}}How do I handle other gestures on widgets?
#Using the GestureDetector, you can listen to a wide range of Gestures such as:
Tap
onTapDown- A pointer that might cause a tap has contacted the screen at a particular location.onTapUp- A pointer that triggers a tap has stopped contacting the screen at a particular location.onTap- A tap has occurred.onTapCancel- The pointer that previously triggered theonTapDownwon't cause a tap.
Double tap
onDoubleTap- The user tapped the screen at the same location twice in quick succession.
Long press
onLongPress- A pointer has remained in contact with the screen at the same location for a long period of time.
Vertical drag
onVerticalDragStart- A pointer has contacted the screen and might begin to move vertically.onVerticalDragUpdate- A pointer in contact with the screen has moved further in the vertical direction.onVerticalDragEnd- A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.
Horizontal drag
onHorizontalDragStart- A pointer has contacted the screen and might begin to move horizontally.onHorizontalDragUpdate- A pointer in contact with the screen has moved further in the horizontal direction.onHorizontalDragEnd- A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.
The following example shows aGestureDetector that rotates the Flutter logo on a double tap:
classSampleAppextendsStatefulWidget{constSampleApp({super.key});@overrideState<SampleApp>createState()=>_SampleAppState();}class_SampleAppStateextendsState<SampleApp>withSingleTickerProviderStateMixin{lateAnimationControllercontroller;lateCurvedAnimationcurve;@overridevoidinitState(){super.initState();controller=AnimationController(vsync:this,duration:constDuration(milliseconds:2000),);curve=CurvedAnimation(parent:controller,curve:Curves.easeIn);}@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Center(child:GestureDetector(onDoubleTap:(){if(controller.isCompleted){controller.reverse();}else{controller.forward();}},child:RotationTransition(turns:curve,child:constFlutterLogo(size:200),),),),);}}Listviews & adapters
#What is the alternative to a ListView in Flutter?
#The equivalent to a ListView in Flutter is … a ListView!
In an Android ListView, you create an adapter and pass it into the ListView, which renders each row with what your adapter returns. However, you have to make sure you recycle your rows, otherwise, you get all sorts of crazy visual glitches and memory issues.
Due to Flutter's immutable widget pattern, you pass a list of widgets to your ListView, and Flutter takes care of making sure that scrolling is fast and smooth.
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:_getListData()),);}List<Widget>_getListData(){List<Widget>widgets=[];for(inti=0;i<100;i++){widgets.add(Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}returnwidgets;}}How do I know which list item is clicked on?
#In Android, the ListView has a method to find out which item was clicked, 'onItemClickListener'. In Flutter, use the touch handling provided by the passed-in widgets.
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:_getListData()),);}List<Widget>_getListData(){List<Widget>widgets=[];for(inti=0;i<100;i++){widgets.add(GestureDetector(onTap:(){developer.log('row tapped');},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i'),),),);}returnwidgets;}}How do I update ListView's dynamically?
#On Android, you update the adapter and callnotifyDataSetChanged.
In Flutter, if you were to update the list of widgets inside asetState(), you would quickly see that your data did not change visually. This is because whensetState() is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to yourListView, it performs a== check, and determines that the twoListViews are the same. Nothing has changed, so no update is required.
For a simple way to update yourListView, create a newList inside ofsetState(), and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>widgets=[];@overridevoidinitState(){super.initState();for(inti=0;i<100;i++){widgets.add(getRow(i));}}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView(children:widgets),);}WidgetgetRow(inti){returnGestureDetector(onTap:(){setState((){widgets=List.from(widgets);widgets.add(getRow(widgets.length));developer.log('row$i');});},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}} The recommended, efficient, and effective way to build a list uses aListView.Builder. This method is great when you have a dynamicList or aList with very large amounts of data. This is essentially the equivalent of RecyclerView on Android, which automatically recycles list elements for you:
import'dart:developer'asdeveloper;import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{List<Widget>widgets=[];@overridevoidinitState(){super.initState();for(inti=0;i<100;i++){widgets.add(getRow(i));}}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:ListView.builder(itemCount:widgets.length,itemBuilder:(context,position){returngetRow(position);},),);}WidgetgetRow(inti){returnGestureDetector(onTap:(){setState((){widgets.add(getRow(widgets.length));developer.log('row$i');});},child:Padding(padding:constEdgeInsets.all(10),child:Text('Row$i')),);}} Instead of creating a "ListView", create aListView.builder that takes two key parameters: the initial length of the list, and anItemBuilder function.
TheItemBuilder function is similar to thegetView function in an Android adapter; it takes a position, and returns the row you want rendered at that position.
Finally, but most importantly, notice that theonTap() function doesn't recreate the list anymore, but instead.adds to it.
Working with text
#How do I set custom fonts on my Text widgets?
#In Android SDK (as of Android O), you create a Font resource file and pass it into the FontFamily param for your TextView.
In Flutter, place the font file in a folder and reference it in thepubspec.yaml file, similar to how you import images.
fonts:-family:MyCustomFontfonts:-asset:fonts/MyCustomFont.ttf-style:italicThen assign the font to yourText widget:
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:constCenter(child:Text('This is a custom font text',style:TextStyle(fontFamily:'MyCustomFont'),),),);}How do I style my Text widgets?
# Along with fonts, you can customize other styling elements on aText widget. The style parameter of aText widget takes aTextStyle object, where you can customize many parameters, such as:
- color
- decoration
- decorationColor
- decorationStyle
- fontFamily
- fontSize
- fontStyle
- fontWeight
- hashCode
- height
- inherit
- letterSpacing
- textBaseline
- wordSpacing
Form input
#For more information on using Forms, seeRetrieve the value of a text field.
What is the equivalent of a "hint" on an Input?
#In Flutter, you can easily show a "hint" or a placeholder text for your input by adding an InputDecoration object to the decoration constructor parameter for the Text Widget.
Center(child:TextField(decoration:InputDecoration(hintText:'This is a hint')),)How do I show validation errors?
#Just as you would with a "hint", pass an InputDecoration object to the decoration constructor for the Text widget.
However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a newInputDecoration object.
import'package:flutter/material.dart';voidmain(){runApp(constSampleApp());}classSampleAppextendsStatelessWidget{constSampleApp({super.key});// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),),home:constSampleAppPage(),);}}classSampleAppPageextendsStatefulWidget{constSampleAppPage({super.key});@overrideState<SampleAppPage>createState()=>_SampleAppPageState();}class_SampleAppPageStateextendsState<SampleAppPage>{String?_errorText;@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Sample App')),body:Center(child:TextField(onSubmitted:(text){setState((){if(!isEmail(text)){_errorText='Error: This is not an email';}else{_errorText=null;}});},decoration:InputDecoration(hintText:'This is a hint',errorText:_getErrorText(),),),),);}String?_getErrorText(){return_errorText;}boolisEmail(Stringem){StringemailRegexp=r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';RegExpregExp=RegExp(emailRegexp);returnregExp.hasMatch(em);}}Flutter plugins
#How do I access the GPS sensor?
#Use thegeolocator community plugin.
How do I access the camera?
# Theimage_picker plugin is popular for accessing the camera.
How do I log in with Facebook?
# To Log in with Facebook, use theflutter_facebook_login community plugin.
How do I use Firebase features?
#Most Firebase functions are covered byfirst party plugins. These plugins are first-party integrations, maintained by the Flutter team:
google_mobile_adsfor Google Mobile Ads for Flutterfirebase_analyticsfor Firebase Analyticsfirebase_authfor Firebase Authfirebase_databasefor Firebase RTDBfirebase_storagefor Firebase Cloud Storagefirebase_messagingfor Firebase Messaging (FCM)flutter_firebase_uifor quick Firebase Auth integrations (Facebook, Google, Twitter and email)cloud_firestorefor Firebase Cloud Firestore
You can also find some third-party Firebase plugins on pub.dev that cover areas not directly covered by the first-party plugins.
How do I build my own custom native integrations?
#If there is platform-specific functionality that Flutter or its community Plugins are missing, you can build your own following thedeveloping packages and plugins page.
Flutter's plugin architecture, in a nutshell, is much like using an Event bus in Android: you fire off a message and let the receiver process and emit a result back to you. In this case, the receiver is code running on the native side on Android or iOS.
How do I use the NDK in my Flutter application?
#If you use the NDK in your current Android application and want your Flutter application to take advantage of your native libraries then it's possible by building a custom plugin.
Your custom plugin first talks to your Android app, where you call yournative functions over JNI. Once a response is ready, send a message back to Flutter and render the result.
Calling native code directly from Flutter is currently not supported.
Themes
#How do I theme my app?
#Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do. Unlike Android where you declare themes in XML and then assign it to your application using AndroidManifest.xml, in Flutter you declare themes in the top level widget.
To take full advantage of Material Components in your app, you can declare a top level widgetMaterialApp as the entry point to your application. MaterialApp is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a WidgetsApp by adding Material specific functionality.
You can also use aWidgetsApp as your app widget, which provides some of the same functionality, but is not as rich asMaterialApp.
To customize the colors and styles of any child components, pass aThemeData object to theMaterialApp widget. For example, in the code below, the color scheme from seed is set to deepPurple and text selection color is red.
import'package:flutter/material.dart';classSampleAppextendsStatelessWidget{constSampleApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Sample App',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.deepPurple),textSelectionTheme:constTextSelectionThemeData(selectionColor:Colors.red,),),home:constSampleAppPage(),);}}Homescreen widgets
#How do I create a homescreen widget?
#Android homescreen widgets cannot be created fully using Flutter. They must use either Jetpack Glance(preferred method) or XML layout code. Using the third-party package,home_widget, you can wire a homescreen widget to Dart code, embed a Flutter component (as an image) in a host widget, and share data to/from Flutter to the homescreen widget.
To provide a richer and more engaging experience, it's recommended to add widget previews to include in the widget picker. For devices running Android 15 and above, generated widget previews allowing the user to see a dynamic and personalized version of the target widget, giving them a glimpse of how it will accurately on their home screen. For more information about the Generated Widget Previews and the fallback options for older devices, check theAdd generated previews to your widget picker documentation page.
Databases and local storage
#How do I access Shared Preferences?
#In Android, you can store a small collection of key-value pairs using the SharedPreferences API.
In Flutter, access this functionality using theShared_Preferences plugin. This plugin wraps the functionality of both Shared Preferences and NSUserDefaults (the iOS equivalent).
import'dart:async';import'package:flutter/material.dart';import'package:shared_preferences/shared_preferences.dart';voidmain(){runApp(constMaterialApp(home:Scaffold(body:Center(child:ElevatedButton(onPressed:_incrementCounter,child:Text('Increment Counter'),),),),),);}Future<void>_incrementCounter()async{SharedPreferencesprefs=awaitSharedPreferences.getInstance();intcounter=(prefs.getInt('counter')??0)+1;awaitprefs.setInt('counter',counter);}How do I access SQLite in Flutter?
#In Android, you use SQLite to store structured data that you can query using SQL.
In Flutter, for macOS, Android, or iOS, access this functionality using theSQFlite plugin.
Debugging
#What tools can I use to debug my app in Flutter?
#Use theDevTools suite for debugging Flutter or Dart apps.
DevTools includes support for profiling, examining the heap, inspecting the widget tree, logging diagnostics, debugging, observing executed lines of code, debugging memory leaks and memory fragmentation. For more information, check out theDevTools documentation.
Notifications
#How do I set up push notifications?
#In Android, you use Firebase Cloud Messaging to set up push notifications for your app.
In Flutter, access this functionality using theFirebase Messaging plugin. For more information on using the Firebase Cloud Messaging API, see thefirebase_messaging plugin documentation.
Unless stated otherwise, the documentation on this site reflects Flutter 3.38.1. Page last updated on 2025-10-30.View source orreport an issue.