diff --git a/src/get-started/flutter-for/xamarin-forms-devs.md b/src/get-started/flutter-for/xamarin-forms-devs.md index 63ceaa2934..5eef7648f5 100644 --- a/src/get-started/flutter-for/xamarin-forms-devs.md +++ b/src/get-started/flutter-for/xamarin-forms-devs.md @@ -3,23 +3,21 @@ title: Flutter for Xamarin.Forms developers description: Learn how to apply Xamarin.Forms developer knowledge when building Flutter apps. --- -This document is meant for Xamarin.Forms developers looking -to apply their existing knowledge to build mobile apps with -Flutter. If you understand the fundamentals of the -Xamarin.Forms framework, then you can use this -document as a jump start to Flutter development. - -Your Android and iOS knowledge and skill set are valuable -when building with Flutter, because Flutter relies on -the native operating system configurations, -similar to how you would configure your native -Xamarin.Forms projects. The Flutter Frameworks -is also similar to how you create a single UI, +This document is meant for Xamarin.Forms developers +looking to apply their existing knowledge +to build mobile apps with Flutter. +If you understand the fundamentals of the Xamarin.Forms framework, +then you can use this document as a jump start to Flutter development. + +Your Android and iOS knowledge and skill set +are valuable when building with Flutter, +because Flutter relies on the native operating system configurations, +similar to how you would configure your native Xamarin.Forms projects. +The Flutter Frameworks is also similar to how you create a single UI, that is used on multiple platforms. -This document can be used as a cookbook by -jumping around and finding questions -that are most relevant to your needs. +This document can be used as a cookbook by jumping around +and finding questions that are most relevant to your needs. ## Project setup @@ -39,7 +37,7 @@ In Flutter, the default main entry point is ```dart void main() { - runApp(MyApp()); + runApp(const MyApp()); } ``` @@ -70,23 +68,28 @@ The following example shows `MyApp`, a simple application `Widget`. ```dart class MyApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const MyApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return Center( - child: Text("Hello World!", textDirection: TextDirection.ltr)); + return const Center( + child: Text( + 'Hello World!', + textDirection: TextDirection.ltr, + ), + ); } } ``` ### How do you create a page? -Xamarin.Forms has many different types of pages; -`ContentPage` is the most common. In Flutter, -you specify an application widget that holds your root page. -You can use a [`MaterialApp`][] widget, -which supports [Material Design][], or you can use a -[`CupertinoApp`][] widget, which supports an iOS-style app, +Xamarin.Forms has many types of pages; +`ContentPage` is the most common. +In Flutter, you specify an application widget that holds your root page. +You can use a [`MaterialApp`][] widget, which supports [Material Design][], +or you can use a [`CupertinoApp`][] widget, which supports an iOS-style app, or you can use the lower level [`WidgetsApp`][], which you can customize in any way you want. @@ -96,9 +99,8 @@ which you can customize in any way you want. The following code defines the home page, a stateful widget. In Flutter, all widgets are immutable, -but two types of widgets are supported: -stateful and stateless. Examples of a stateless widget -are titles, icons, or images. +but two types of widgets are supported: _Stateful_ and _Stateless_. +Examples of a stateless widget are titles, icons, or images. The following example uses `MaterialApp`, which holds its root page in the `home` property. @@ -106,14 +108,13 @@ which holds its root page in the `home` property. ```dart class MyApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const MyApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } @@ -123,15 +124,15 @@ class MyApp extends StatelessWidget { From here, your actual first page is another `Widget`, in which you create your state. -A stateful widget, such as MyHomePage below, consists of two parts. -The first part, which is itself immutable, creates a State object -that holds the state of the object. The State object persists over +A _Stateful_ widget, such as `MyHomePage` below, consists of two parts. +The first part, which is itself immutable, creates a `State` object +that holds the state of the object. The `State` object persists over the life of the widget. ```dart class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @@ -142,9 +143,10 @@ class MyHomePage extends StatefulWidget { The `State` object implements the `build()` method for the stateful widget. -When the state of the widget tree changes, call `setState()`, which triggers -a build of that portion of the UI. Make sure to call `setState()` only -when necessary, and only on the part of the widget tree that has changed, +When the state of the widget tree changes, call `setState()`, +which triggers a build of that portion of the UI. +Make sure to call `setState()` only when necessary, +and only on the part of the widget tree that has changed, or it can result in poor UI performance. @@ -172,12 +174,12 @@ class _MyHomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( + const Text( 'You have pushed the button this many times:', ), Text( '$_counter', - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), ], ), @@ -185,19 +187,19 @@ class _MyHomePageState extends State { floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', - child: Icon(Icons.add), + child: const Icon(Icons.add), ), ); } } ``` -In Flutter, -the UI (also known as widget tree), is immutable, meaning you -can't change its state once it's built. You change fields in your -`State` class, then call `setState()` to rebuild the entire widget tree again. +In Flutter, the UI (also known as widget tree), is immutable, +meaning you can't change its state once it's built. +You change fields in your `State` class, then call `setState()` +to rebuild the entire widget tree again. -This way of generating UI is different than Xamarin.Forms, +This way of generating UI is different from Xamarin.Forms, but there are many benefits to this approach. ## Views @@ -205,75 +207,80 @@ but there are many benefits to this approach. ### What is the equivalent of a Page or Element in Flutter? {{site.alert.secondary}} - How is react-style, or _declarative_, programming different than the + How is react-style, or _declarative_, programming different from the traditional imperative style? For a comparison, see [Introduction to declarative UI][]. {{site.alert.end}} -`ContentPage`, `TabbedPage`, `FlyoutPage` are all types of pages you -might in a Xamarin.Forms application. These pages would then hold -`Element`s to display the various controls. In Xamarin.Forms an `Entry` -or `Button` are examples of an `Element`. +`ContentPage`, `TabbedPage`, `FlyoutPage` are all types of pages +you might use in a Xamarin.Forms application. +These pages would then hold `Element`s to display the various controls. +In Xamarin.Forms an `Entry` or `Button` are examples of an `Element`. -In Flutter, almost everything is a widget. A `Page`, called a `Route` in -Flutter, is a widget. Buttons, progress bars, and animation controllers -are all widgets. When building a route, you create a widget tree. +In Flutter, almost everything is a widget. +A `Page`, called a `Route` in Flutter, is a widget. +Buttons, progress bars, and animation controllers are all widgets. +When building a route, you create a widget tree. Flutter includes the [Material Components][] library. -These are widgets that implement the -[Material Design guidelines][]. Material Design is a -flexible design system [optimized for all platforms][], -including iOS. +These are widgets that implement the [Material Design guidelines][]. +Material Design is a flexible design system +[optimized for all platforms][], including iOS. -But Flutter is flexible and expressive enough to implement -any design language. For example, on iOS, -you can use the [Cupertino widgets][] -to produce an interface that looks like -[Apple's iOS design language][]. +But Flutter is flexible and expressive enough +to implement any design language. +For example, on iOS, you can use the [Cupertino widgets][] +to produce an interface that looks like [Apple's iOS design language][]. ### How do I update widgets? -In Xamarin.Forms, each `Page` or `Element` is a stateful class, that has -properties and methods. You update your `Element` by updating a property, +In Xamarin.Forms, each `Page` or `Element` is a stateful class, +that has properties and methods. +You update your `Element` by updating a property, and this is propagated down to the native control. In Flutter, `Widget`s are immutable and you can't directly update them by changing a property, instead you have to work with the widget's state. -This is where the concept of Stateful vs Stateless widgets comes from. A -`StatelessWidget` is just what it sounds like—a widget with no state -information. +This is where the concept of Stateful vs Stateless widgets comes from. +A `StatelessWidget` 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. +you are describing doesn't depend on anything +other than the configuration information in the object. -For example, in Xamarin.Forms, this is similar to placing an `Image` -with your logo. The logo is not going to change during runtime, so -use a `StatelessWidget` in Flutter. +For example, in Xamarin.Forms, this is similar +to placing an `Image` with your logo. +The logo is not going to change during runtime, +so use a `StatelessWidget` 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 -with `StatefulWidget` and tell the Flutter framework that the widget’s -`State` 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 the -`StatefulWidget` has a `State` object that stores state data across frames -and restores it. +after making an HTTP call or a user interaction, +then you have to work with `StatefulWidget` +and tell the Flutter framework that +the widget’s `State` 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 +the `StatefulWidget` has a `State` 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 a `StatelessWidget`. A common -`StatelessWidget` is the `Text` widget. If you look at the implementation of -the `Text` widget you'll find it subclasses `StatelessWidget`. +The following example shows how to use a `StatelessWidget`. +A common `StatelessWidget` is the `Text` widget. +If you look at the implementation of the `Text` widget +you'll find it subclasses `StatelessWidget`. ```dart -Text( +const Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), ); @@ -282,64 +289,61 @@ Text( As you can see, the `Text` 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 a `FloatingActionButton`? +But, what if you want to make "I Like Flutter" change dynamically, +for example, when clicking a `FloatingActionButton`? -To achieve this, wrap the `Text` widget in a `StatefulWidget` and -update it when the user clicks the button, as shown in the following -example: +To achieve this, wrap the `Text` widget in a `StatefulWidget` +and update it when the user clicks the button, +as shown in the following example: ```dart import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - // Default placeholder text - String textToShow = "I Like Flutter"; + /// Default placeholder text + String textToShow = 'I Like Flutter'; void _updateText() { setState(() { // Update the text - textToShow = "Flutter is Awesome!"; + textToShow = 'Flutter is Awesome!'; }); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), + appBar: AppBar(title: const Text('Sample App')), body: Center(child: Text(textToShow)), floatingActionButton: FloatingActionButton( onPressed: _updateText, tooltip: 'Update Text', - child: Icon(Icons.update), + child: const Icon(Icons.update), ), ); } @@ -359,16 +363,14 @@ The following example shows how to display a simple widget with padding: @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), + appBar: AppBar(title: const Text('Sample App')), body: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( - padding: EdgeInsets.only(left: 20.0, right: 30.0), + padding: const EdgeInsets.only(left: 20.0, right: 30.0), ), onPressed: () {}, - child: Text('Hello'), + child: const Text('Hello'), ), ), ); @@ -388,34 +390,33 @@ In Flutter, because widgets are immutable there is no direct equivalent. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag. -The following example shows how to toggle between two widgets when the user -clicks the `FloatingActionButton`: +The following example shows how to toggle between two widgets +when the user clicks the `FloatingActionButton`: ```dart class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - // Default value for toggle + /// Default value for toggle bool toggle = true; void _toggle() { setState(() { @@ -423,30 +424,25 @@ class _SampleAppPageState extends State { }); } - _getToggleChild() { + Widget _getToggleChild() { if (toggle) { - return Text('Toggle One'); - } else { - return CupertinoButton( - onPressed: () {}, - child: Text('Toggle Two'), - ); + return const Text('Toggle One'); } + return CupertinoButton( + onPressed: () {}, + child: const Text('Toggle Two'), + ); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: Center( - child: _getToggleChild(), - ), + appBar: AppBar(title: const Text('Sample App')), + body: Center(child: _getToggleChild()), floatingActionButton: FloatingActionButton( onPressed: _toggle, tooltip: 'Update Text', - child: Icon(Icons.update), + child: const Icon(Icons.update), ), ); } @@ -465,28 +461,31 @@ to perform the required animations. ``` Then in code behind, or a behavior, this would fade in the image, -over a 1 second period. +over a 1-second period. ```csharp myImage.FadeTo(0, 1000); ``` -In Flutter, you animate widgets using the animation library by wrapping -widgets inside an animated widget. Use an `AnimationController`, -which is an `Animation` that can pause, seek, stop and reverse -the animation. It requires a `Ticker` 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 more -`Animation`s and attach them to the controller. - -For example, you might use `CurvedAnimation` to implement an animation -along an interpolated curve. In this sense, the controller -is the "master" source of the animation progress and the `CurvedAnimation` -computes the curve that replaces the controller's default linear motion. +In Flutter, you animate widgets using the animation library +by wrapping widgets inside an animated widget. +Use an `AnimationController`, which is an `Animation` +that can pause, seek, stop and reverse the animation. +It requires a `Ticker` 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 more`Animation`s and attach them to the controller. + +For example, you might use `CurvedAnimation` +to implement an animation along an interpolated curve. +In this sense, the controller is the "master" source of the animation progress +and the `CurvedAnimation` 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 the `Animation` to an animated -property of a widget, such as the opacity of a `FadeTransition`, +When building the widget tree, you assign the `Animation` +to an animated property of a widget, +such as the opacity of a `FadeTransition`, and tell the controller to start the animation. The following example shows how to write a `FadeTransition` that fades @@ -497,60 +496,64 @@ the widget into a logo when you press the `FloatingActionButton`: import 'package:flutter/material.dart'; void main() { - runApp(FadeAppTest()); + runApp(const FadeAppTest()); } class FadeAppTest extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const FadeAppTest({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Fade Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: MyFadeTest(title: 'Fade Demo'), ); } } class MyFadeTest extends StatefulWidget { - MyFadeTest({Key key, this.title}) : super(key: key); + const MyFadeTest({Key? key, required this.title}) : super(key: key); + final String title; + @override _MyFadeTest createState() => _MyFadeTest(); } class _MyFadeTest extends State with TickerProviderStateMixin { - AnimationController controller; - CurvedAnimation curve; + late final AnimationController controller; + late final CurvedAnimation curve; @override void initState() { super.initState(); - controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); - curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); + controller = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + ); + curve = CurvedAnimation( + parent: controller, + curve: Curves.easeIn, + ); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( - child: Container( - child: FadeTransition( - opacity: curve, - child: FlutterLogo( - size: 100.0, - )))), + child: FadeTransition( + opacity: curve, + child: const FlutterLogo(size: 100.0), + ), + ), floatingActionButton: FloatingActionButton( - tooltip: 'Fade', - child: Icon(Icons.brush), onPressed: () { controller.forward(); }, + tooltip: 'Fade', + child: const Icon(Icons.brush), ), ); } @@ -562,7 +565,7 @@ the [Animations tutorial][], and the [Animations overview][]. ### How do I draw/paint on the screen? -Xamarin.Forms never had a built in way to draw directly on the screen. +Xamarin.Forms never had a built-in way to draw directly on the screen. Many would use SkiaSharp, if they needed a custom image drawn. In Flutter, you have direct access to the Skia Canvas and can easily draw on screen. @@ -579,48 +582,71 @@ see Collin's answer on [StackOverflow][]. ```dart import 'package:flutter/material.dart'; -void main() => runApp(MaterialApp(home: DemoApp())); +void main() { + runApp(const MaterialApp(home: DemoApp())); +} class DemoApp extends StatelessWidget { - Widget build(BuildContext context) => Scaffold(body: Signature()); + const DemoApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => Scaffold(body: const Signature()); } class Signature extends StatefulWidget { + const Signature({Key? key}) : super(key: key); + + @override SignatureState createState() => SignatureState(); } class SignatureState extends State { - List _points = []; + List _points = []; + + void _onPanUpdate(DragUpdateDetails details) { + setState(() { + final RenderBox referenceBox = context.findRenderObject() as RenderBox; + final Offset localPosition = referenceBox.globalToLocal( + details.globalPosition, + ); + _points = List.from(_points)..add(localPosition); + }); + } + + @override Widget build(BuildContext context) { return GestureDetector( - onPanUpdate: (DragUpdateDetails details) { - setState(() { - RenderBox referenceBox = context.findRenderObject(); - Offset localPosition = - referenceBox.globalToLocal(details.globalPosition); - _points = List.from(_points)..add(localPosition); - }); - }, + onPanUpdate: _onPanUpdate, onPanEnd: (DragEndDetails details) => _points.add(null), - child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite), + child: CustomPaint( + painter: SignaturePainter(_points), + size: Size.infinite, + ), ); } } class SignaturePainter extends CustomPainter { - SignaturePainter(this.points); - final List points; + const SignaturePainter(this.points); + + final List points; + + @override void paint(Canvas canvas, Size size) { - var paint = Paint() + final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { - if (points[i] != null && points[i + 1] != null) - canvas.drawLine(points[i], points[i + 1], paint); + if (points[i] != null && points[i + 1] != null) { + canvas.drawLine(points[i]!, points[i + 1]!, paint); + } } } - bool shouldRepaint(SignaturePainter other) => other.points != points; + + @override + bool shouldRepaint(SignaturePainter oldDelegate) => + oldDelegate.points != points; } ``` @@ -638,24 +664,28 @@ implement methods that achieve the desired behavior. In Flutter, build a custom widget by [composing][] smaller widgets (instead of extending them). -It is somewhat similar to implementing a custom control based off a -`Grid` with numerous `VisualElement`s added in, while extending with -custom logic. +It is somewhat similar to implementing a custom control +based off a `Grid` with numerous `VisualElement`s added in, +while extending with custom logic. -For example, how do you build a `CustomButton` that takes a label in -the constructor? Create a CustomButton that composes a `ElevatedButton` +For example, how do you build a `CustomButton` +that takes a label in the constructor? +Create a CustomButton that composes a `ElevatedButton` with a label, rather than by extending `ElevatedButton`: ```dart class CustomButton extends StatelessWidget { - final String label; + const CustomButton(this.label, {Key? key}) : super(key: key); - CustomButton(this.label); + final String label; @override Widget build(BuildContext context) { - return ElevatedButton(onPressed: () {}, child: Text(label)); + return ElevatedButton( + onPressed: () {}, + child: Text(label), + ); } } ``` @@ -666,8 +696,8 @@ Then use `CustomButton`, just as you'd use any other Flutter widget: ```dart @override Widget build(BuildContext context) { - return Center( - child: CustomButton("Hello"), + return const Center( + child: CustomButton('Hello'), ); } ``` @@ -676,17 +706,20 @@ Widget build(BuildContext context) { ### How do I navigate between pages? -In Xamarin.Forms, the `NavigationPage` class provides a hierarchical -navigation experience where the user is able to navigate through pages, +In Xamarin.Forms, the `NavigationPage` class +provides a hierarchical navigation experience +where the user is able to navigate through pages, forwards and backwards. -Flutter has a similar implementation, using a `Navigator` and -`Routes`. A `Route` is an abstraction for a `Page` of an app, and -a `Navigator` is a [widget][] that manages routes. +Flutter has a similar implementation, +using a `Navigator` and `Routes`. +A `Route` is an abstraction for a `Page` of an app, +and a `Navigator` is a [widget][] that manages routes. -A route roughly maps to a `Page`. The navigator works in a similar way to the -Xamarin.Forms `NavigationPage`, in that it can `push()` and `pop()` routes -depending on whether you want to navigate to, or back from, a view. +A route roughly maps to a `Page`. +The navigator works in a similar way to the Xamarin.Forms `NavigationPage`, +in that it can `push()` and `pop()` routes depending on +whether you want to navigate to, or back from, a view. To navigate between pages, you have a couple options: @@ -698,14 +731,16 @@ The following example builds a `Map`. ```dart void main() { - runApp(MaterialApp( - home: MyAppHome(), // becomes the route named '/' - routes: { - '/a': (BuildContext context) => MyPage(title: 'page A'), - '/b': (BuildContext context) => MyPage(title: 'page B'), - '/c': (BuildContext context) => MyPage(title: 'page C'), - }, - )); + runApp( + MaterialApp( + home: const MyAppHome(), // becomes the route named '/' + routes: { + '/a': (BuildContext context) => const MyPage(title: 'page A'), + '/b': (BuildContext context) => const MyPage(title: 'page B'), + '/c': (BuildContext context) => const MyPage(title: 'page C'), + }, + ), + ); } ``` @@ -721,11 +756,12 @@ Pushing a route to the stack moves to that route. Popping a route from the stack, returns to the previous route. This is done by awaiting on the `Future` returned by `push()`. -`Async`/`await` is very similar to the .NET implementation and is -explained in more detail in [Async UI][]. +`async`/`await` is very similar to the .NET implementation +and is explained in more detail in [Async UI][]. -For example, to start a `location` route that lets the user select their -location, you might do the following: +For example, to start a `location` route +that lets the user select their location, +you might do the following: ```dart @@ -737,58 +773,62 @@ location, pop the stack with the result: ```dart -Navigator.of(context).pop({"lat":43.821757,"long":-79.226392}); +Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392}); ``` ### How do I navigate to another app? -In Xamarin.Forms, to send the user to another application, you use a -specific URI scheme, using `Device.OpenUrl("mailto://")` +In Xamarin.Forms, to send the user to another application, +you use a specific URI scheme, using `Device.OpenUrl("mailto://")`. To implement this functionality in Flutter, -create a native platform integration, -or use an [existing plugin][], such as -[`url_launcher`][], available with -many other packages on [pub.dev][]. +create a native platform integration, or use an [existing plugin][], +such as[`url_launcher`][], available with many other packages on [pub.dev][]. ## Async UI ### What is the equivalent of Device.BeginOnMainThread() in Flutter? -Dart has a single-threaded execution model, with support for `Isolate`s -(a way to run Dart code on another thread), an event loop, and -asynchronous programming. Unless you spawn an `Isolate`, your Dart code -runs in the main UI thread and is driven by an event loop. +Dart has a single-threaded execution model, +with support for `Isolate`s (a way to run Dart codes on another thread), +an event loop, and asynchronous programming. +Unless you spawn an `Isolate`, +your Dart code runs in the main UI thread +and is driven by an event loop. -Dart's single-threaded model doesn't mean you need to run everything as a -blocking operation that causes the UI to freeze. Much like Xamarin.Forms, -you need to keep the UI thread free. You would use `async`/`await` to perform -tasks, where you must wait for the response. +Dart's single-threaded model doesn't mean you need to run everything +as a blocking operation that causes the UI to freeze. +Much like Xamarin.Forms, you need to keep the UI thread free. +You would use `async`/`await` to perform tasks, +where you must wait for the response. In Flutter, use the asynchronous facilities that the Dart language provides, also named `async`/`await`, to perform asynchronous work. -This is very similar to C# and should be very easy to use for any -Xamarin.Forms developer. +This is very similar to C# and should be very easy to use +for any Xamarin.Forms developer. For example, you can run network code without causing the UI to hang by using `async`/`await` and letting Dart do the heavy lifting: ```dart -loadData() async { - String dataURL = "https://jsonplaceholder.typicode.com/posts"; - http.Response response = await http.get(dataURL); +Future loadData() async { + final Uri dataURL = Uri.parse( + 'https://jsonplaceholder.typicode.com/posts', + ); + final http.Response response = await http.get(dataURL); setState(() { - widgets = jsonDecode(response.body); + data = jsonDecode(response.body); }); } ``` -Once the awaited network call is done, update the UI by calling `setState()`, -which triggers a rebuild of the widget sub-tree and updates the data. +Once the awaited network call is done, +update the UI by calling `setState()`, +which triggers a rebuild of the widget subtree and updates the data. -The following example loads data asynchronously and displays it -in a `ListView`: +The following example loads data asynchronously +and displays it in a `ListView`: ```dart @@ -798,31 +838,30 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - List widgets = []; + List> data = >[]; @override void initState() { @@ -830,63 +869,70 @@ class _SampleAppPageState extends State { loadData(); } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: ListView.builder( - itemCount: widgets.length, - itemBuilder: (BuildContext context, int position) { - return getRow(position); - })); + Future loadData() async { + final Uri dataURL = Uri.parse( + 'https://jsonplaceholder.typicode.com/posts', + ); + final http.Response response = await http.get(dataURL); + setState(() { + data = jsonDecode(response.body); + }); } - Widget getRow(int i) { + Widget getRow(int index) { return Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row ${widgets[i]["title"]}") + padding: const EdgeInsets.all(10.0), + child: Text('Row ${data[index]['title']}'), ); } - loadData() async { - String dataURL = "https://jsonplaceholder.typicode.com/posts"; - http.Response response = await http.get(dataURL); - setState(() { - widgets = jsonDecode(response.body); - }); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sample App')), + body: ListView.builder( + itemCount: widgets.length, + itemBuilder: (BuildContext context, int index) { + return getRow(index); + }, + ), + ); } } ``` -Refer to the next section for more information on doing work in the -background, and how Flutter differs from Android. +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? Since Flutter is single threaded and runs an event loop, -you don't have to worry about thread management or spawning -background threads. This is very similar to Xamarin.Forms. +you don't have to worry about thread management +or spawning background threads. +This is very similar to Xamarin.Forms. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use `async`/`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 an `Isolate` -to avoid blocking the event loop, like you would keep _any_ sort of -work out of the main thread. This is similar to when you -move things to a different thread via `Task.Run()` in Xamarin.Forms. +that keeps the CPU busy, +you want to move it to an `Isolate` to avoid blocking the event loop, +like you would keep _any_ sort of work out of the main thread. +This is similar to when you move things to a different +thread via `Task.Run()` in Xamarin.Forms. For I/O-bound work, declare the function as an `async` function, and `await` on long-running tasks inside the function: ```dart -loadData() async { - String dataURL = "https://jsonplaceholder.typicode.com/posts"; - http.Response response = await http.get(dataURL); +Future loadData() async { + final Uri dataURL = Uri.parse( + 'https://jsonplaceholder.typicode.com/posts', + ); + final http.Response response = await http.get(dataURL); setState(() { - widgets = jsonDecode(response.body); + data = jsonDecode(response.body); }); } ``` @@ -894,105 +940,105 @@ loadData() async { This is how you would typically do network or database calls, which are both I/O operations. -However, there are times when you might be processing a large amount -of data and your UI hangs. In Flutter, use `Isolate`s to take advantage of -multiple CPU cores to do long-running or computationally intensive tasks. +However, there are times when you might be processing +a large amount of data and your UI hangs. +In Flutter, use `Isolate`s 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 is a difference between -`Task.Run()`. This means you can’t access variables from the main thread, +Isolates are separate execution threads that +do not share any memory with the main execution memory heap. +This is a difference between `Task.Run()`. +This means you can’t access variables from the main thread, or update your UI by calling `setState()`. -The following example shows, in a simple isolate, how to share data back to -the main thread to update the UI. +The following example shows, in a simple isolate, +how to share data back to the main thread to update the UI. ```dart -loadData() async { - ReceivePort receivePort = ReceivePort(); +Future loadData() async { + final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); - // The 'echo' isolate sends its SendPort as the first message. - SendPort sendPort = await receivePort.first; - - List msg = await sendReceive( + // The 'echo' isolate sends its SendPort as the first message + final SendPort sendPort = await receivePort.first as SendPort; + final List> msg = await sendReceive( sendPort, - "https://jsonplaceholder.typicode.com/posts", + 'https://jsonplaceholder.typicode.com/posts', ); - setState(() { - widgets = msg; + data = msg; }); } -// The entry point for the isolate. -static dataLoader(SendPort sendPort) async { +// The entry point for the isolate +static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. - ReceivePort port = ReceivePort(); + final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); + await for (final dynamic msg in port) { + final String url = msg[0] as String; + final SendPort replyTo = msg[1] as SendPort; - await for (var msg in port) { - String data = msg[0]; - SendPort replyTo = msg[1]; - - String dataURL = data; - http.Response response = await http.get(dataURL); + final Uri dataURL = Uri.parse(url); + final http.Response response = await http.get(dataURL); // Lots of JSON to parse - replyTo.send(jsonDecode(response.body)); + replyTo.send(jsonDecode(response.body) as List>); } } -Future sendReceive(SendPort port, msg) { - ReceivePort response = ReceivePort(); - port.send([msg, response.sendPort]); - return response.first; +Future>> sendReceive(SendPort port, String msg) { + final ReceivePort response = ReceivePort(); + port.send([msg, response.sendPort]); + return response.first as Future>>; } ``` -Here, `dataLoader()` is the `Isolate` that runs in its own separate -execution thread. In the isolate you can perform more CPU intensive +Here, `dataLoader()` is the `Isolate` 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: + ```dart +import 'dart:async'; import 'dart:convert'; +import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'dart:async'; -import 'dart:isolate'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - List widgets = []; + List> data = >[]; @override void initState() { @@ -1000,123 +1046,113 @@ class _SampleAppPageState extends State { loadData(); } - showLoadingDialog() { - if (widgets.length == 0) { - return true; - } - - return false; - } - - getBody() { - if (showLoadingDialog()) { - return getProgressDialog(); - } else { - return getListView(); - } - } - - getProgressDialog() { - return Center(child: CircularProgressIndicator()); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: getBody()); - } - - ListView getListView() => ListView.builder( - itemCount: widgets.length, - itemBuilder: (BuildContext context, int position) { - return getRow(position); - }); - - Widget getRow(int i) { - return Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row ${widgets[i]["title"]}"), - ); - } + bool get showLoadingDialog => data.isEmpty; - loadData() async { - ReceivePort receivePort = ReceivePort(); + Future loadData() async { + final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); - // The 'echo' isolate sends its SendPort as the first message. - SendPort sendPort = await receivePort.first; - - List msg = await sendReceive( + // The 'echo' isolate sends its SendPort as the first message + final SendPort sendPort = await receivePort.first as SendPort; + final List> msg = await sendReceive( sendPort, - "https://jsonplaceholder.typicode.com/posts", + 'https://jsonplaceholder.typicode.com/posts', ); - setState(() { - widgets = msg; + data = msg; }); } - // the entry point for the isolate - static dataLoader(SendPort sendPort) async { + // The entry point for the isolate + static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. - ReceivePort port = ReceivePort(); + final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); + await for (final dynamic msg in port) { + final String url = msg[0] as String; + final SendPort replyTo = msg[1] as SendPort; - await for (var msg in port) { - String data = msg[0]; - SendPort replyTo = msg[1]; - - String dataURL = data; - http.Response response = await http.get(dataURL); + final Uri dataURL = Uri.parse(url); + final http.Response response = await http.get(dataURL); // Lots of JSON to parse - replyTo.send(jsonDecode(response.body)); + replyTo.send(jsonDecode(response.body) as List>); } } - Future sendReceive(SendPort port, msg) { - ReceivePort response = ReceivePort(); - port.send([msg, response.sendPort]); - return response.first; + Future>> sendReceive(SendPort port, String msg) { + final ReceivePort response = ReceivePort(); + port.send([msg, response.sendPort]); + return response.first as Future>>; + } + + Widget getBody() { + if (showLoadingDialog) { + return getProgressDialog(); + } + return getListView(); + } + + Widget getProgressDialog() { + return const Center(child: CircularProgressIndicator()); + } + + ListView getListView() { + return ListView.builder( + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + return getRow(index); + }, + ); + } + + Widget getRow(int index) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Text('Row ${data[index]['title']}'), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sample App')), + body: getBody(), + ); } } ``` ### How do I make network requests? -In Xamarin.Forms you would use `HttpClient`. Making a network call in Flutter -is easy when you use the popular [`http` package][]. -This abstracts away a lot of the networking that you might normally -implement yourself, making it simple to make network calls. +In Xamarin.Forms you would use `HttpClient`. +Making a network call in Flutter is easy +when you use the popular [`http` package][]. +This abstracts away a lot of the networking +that you might normally implement yourself, +making it simple to make network calls. To use the `http` package, add it to your dependencies in `pubspec.yaml`: ```yaml dependencies: - ... - http: ^0.11.3+16 + http: ^0.13.4 ``` -To make a network request, call `await` on the `async` function `http.get()`: +To make a network request, +call `await` on the `async` function `http.get()`: ```dart -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -[...] - loadData() async { - String dataURL = "https://jsonplaceholder.typicode.com/posts"; - http.Response response = await http.get(dataURL); - setState(() { - widgets = jsonDecode(response.body); - }); - } +Future loadData() async { + final Uri dataURL = Uri.parse( + 'https://jsonplaceholder.typicode.com/posts', + ); + final http.Response response = await http.get(dataURL); + setState(() { + widgets = jsonDecode(response.body); + }); } ``` @@ -1126,49 +1162,50 @@ In Xamarin.Forms you would typically create a loading indicator, either directly in XAML or through a 3rd party plugin such as AcrDialogs. In Flutter, use a `ProgressIndicator` 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. +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. If `showLoadingDialog()` is `true` (when `widgets.length == 0`), -then render the `ProgressIndicator`. Otherwise, render the -`ListView` with the data returned from a network call. +In the example below, the build function is separated into three different +functions. If `showLoadingDialog` is `true` +(when `widgets.length == 0`), then render the `ProgressIndicator`. +Otherwise, render the `ListView` with the data returned from a network call. ```dart +import 'dart:async'; import 'dart:convert'; +import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - List widgets = []; + List> data = >[]; @override void initState() { @@ -1176,50 +1213,51 @@ class _SampleAppPageState extends State { loadData(); } - showLoadingDialog() { - return widgets.length == 0; + bool get showLoadingDialog => data.isEmpty; + + Future loadData() async { + final Uri dataURL = Uri.parse( + 'https://jsonplaceholder.typicode.com/posts', + ); + final http.Response response = await http.get(dataURL); + setState(() { + widgets = jsonDecode(response.body); + }); } - getBody() { - if (showLoadingDialog()) { + Widget getBody() { + if (showLoadingDialog) { return getProgressDialog(); - } else { - return getListView(); } + return getListView(); } - getProgressDialog() { - return Center(child: CircularProgressIndicator()); + Widget getProgressDialog() { + return const Center(child: CircularProgressIndicator()); } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: getBody()); + ListView getListView() { + return ListView.builder( + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + return getRow(index); + }, + ); } - ListView getListView() => ListView.builder( - itemCount: widgets.length, - itemBuilder: (BuildContext context, int position) { - return getRow(position); - }); - - Widget getRow(int i) { + Widget getRow(int index) { return Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row ${widgets[i]["title"]}"), + padding: const EdgeInsets.all(10.0), + child: Text('Row ${data[index]['title']}'), ); } - loadData() async { - String dataURL = "https://jsonplaceholder.typicode.com/posts"; - http.Response response = await http.get(dataURL); - setState(() { - widgets = jsonDecode(response.body); - }); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sample App')), + body: getBody(), + ); } } ``` @@ -1229,13 +1267,14 @@ class _SampleAppPageState extends State { ### Where do I store my image files? Xamarin.Forms has no platform independent way of storing images, -you had to place images in the iOS `xcasset` folder or on Android, -in the various `drawable` folders. +you had to place images in the iOS `xcasset` folder, +or on Android in the various `drawable` folders. While Android and iOS treat resources and assets as distinct items, -Flutter apps have only assets. All resources that would live in the -`Resources/drawable-*` folders on Android, are placed in an assets -folder for Flutter. +Flutter apps have only assets. +All resources that would live in the +`Resources/drawable-*` folders on Android, +are placed in an assets' folder for Flutter. Flutter follows a simple density-based format like iOS. Assets might be `1.0x`, `2.0x`, `3.0x`, or any other multiplier. @@ -1246,19 +1285,19 @@ 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 -the `pubspec.yaml` file, and Flutter picks them up. +| 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 the `pubspec.yaml` file, and Flutter picks them up. To add a new image asset called `my_icon.png` to our Flutter project, for example, and deciding that it should live in a folder we @@ -1266,9 +1305,8 @@ arbitrarily called `images`, you would put the base image (1.0x) in the `images` folder, and all the other variants in sub-folders called with the appropriate ratio multiplier: - -```dart +``` images/my_icon.png // Base: 1.0x image images/2.0x/my_icon.png // 2.0x image images/3.0x/my_icon.png // 3.0x image @@ -1281,110 +1319,114 @@ assets: - images/my_icon.jpeg ``` -You can then access your images using `AssetImage`: +You can directly access your images in an `Image.asset` widget: ```dart -return AssetImage("images/a_dot_burr.jpeg"); +@override +Widget build(BuildContext context) { + return Image.asset('images/my_icon.png'); +} ``` -or directly in an `Image` widget: +or using `AssetImage`: ```dart @override Widget build(BuildContext context) { - return Image.asset("images/my_image.png"); + return const Image( + image: AssetImage('images/my_image.png'), + ); } ``` -More detailed information can be found in -[Adding assets and images][]. +More detailed information can be found in [Adding assets and images][]. ### Where do I store strings? How do I handle localization? -Unlike .NET which has `resx` files, Flutter currently doesn't -have a dedicated resources-like system for strings. At the moment, -the best practice is to hold your copy text in a class as static -fields and accessing them from there. For example: +Unlike .NET which has `resx` files, +Flutter doesn't currently have a dedicated system for handling strings. +At the moment, the best practice is to declare your copy text +in a class as static fields and access them from there. For example: ```dart class Strings { - static String welcomeMessage = "Welcome To Flutter"; + static const String welcomeMessage = 'Welcome To Flutter'; } ``` -Then in your code, you can access your strings as such: +You can access your strings as such: ```dart Text(Strings.welcomeMessage) ``` -By default, Flutter only supports US English for its strings. If you need to -add support for other languages, include the `flutter_localizations` -package. You might also need to add Dart's [`intl`][] +By default, Flutter only supports US English for its strings. +If you need to add support for other languages, +include the `flutter_localizations` package. +You might also need to add Dart's [`intl`][] package to use i10n machinery, such as date/time formatting. ```yaml dependencies: - # ... flutter_localizations: sdk: flutter - intl: "^0.15.6" + intl: '^0.17.0' ``` To use the `flutter_localizations` package, -specify the `localizationsDelegates` and `supportedLocales` on the app widget: +specify the `localizationsDelegates` and +`supportedLocales` on the app widget: ```dart import 'package:flutter_localizations/flutter_localizations.dart'; MaterialApp( - localizationsDelegates: const [ - // Add app-specific localization delegate[s] here. - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: const [ + localizationsDelegates: const >[ + // Add app-specific localization delegate[s] here + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: const [ Locale('en', 'US'), // English Locale('he', 'IL'), // Hebrew // ... other locales the app supports ], - // ... ) ``` The delegates contain the actual localized values, -while the `supportedLocales` defines which locales -the app supports. The above example uses a `MaterialApp`, -so it has both a `GlobalWidgetsLocalizations` for the base -widgets localized values, and a `MaterialWidgetsLocalizations` -for the Material widgets localizations. -If you use `WidgetsApp` for your app, you don't -need the latter. Note that these two delegates contain "default" -values, but you'll need to provide one or more delegates +while the `supportedLocales` defines which locales the app supports. +The above example uses a `MaterialApp`, +so it has both a `GlobalWidgetsLocalizations` +for the base widgets localized values, +and a `MaterialWidgetsLocalizations` for the Material widgets localizations. +If you use `WidgetsApp` for your app, you don't need the latter. +Note that these two delegates contain "default" values, +but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too. When initialized, the `WidgetsApp` (or `MaterialApp`) creates a [`Localizations`][] widget for you, -with the delegates you specify. The current locale for -the device is always accessible from the `Localizations` -widget from the current context (in the form of a `Locale` object), -or using the [`Window.locale`][]. +with the delegates you specify. +The current locale for the device is always accessible +from the `Localizations` widget from the current context +(in the form of a `Locale` object), or using the [`Window.locale`][]. -To access localized resources, use the `Localizations.of()` method to -access a specific localizations class that is provided by a given delegate. -Use the [`intl_translation`][] package to extract translatable -copy to [arb][] files for translating, -and importing them back into the app for using them with `intl`. +To access localized resources, use the `Localizations.of()` method +to access a specific localizations class that is provided by a given delegate. +Use the [`intl_translation`][] package to extract translatable copy +to [arb][] files for translating, and importing them back into the app +for using them with `intl`. For further details on internationalization and localization in Flutter, -see the [internationalization guide][], -which has sample code with and without the `intl` package. +see the [internationalization guide][], which has sample code +with and without the `intl` package. ### Where is my project file? @@ -1396,48 +1438,50 @@ files within the same directory are considered part of the project. ### What is the equivalent of Nuget? How do I add dependencies? -In the .NET eco-system, native Xamarin projects and Xamarin.Forms -projects had access to Nuget and the inbuilt package management system. +In the .NET ecosystem, native Xamarin projects and Xamarin.Forms projects +had access to Nuget and the built-in package management system. Flutter apps contain a native Android app, native iOS app and Flutter app. In Android, you add dependencies by adding to your Gradle build script. In iOS, you add dependencies by adding to your `Podfile`. 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. +The tools delegate the building of the native Android and iOS wrapper apps +to the respective build systems. -In general, use `pubspec.yaml` to declare external dependencies to use in -Flutter. A good place to find Flutter packages is on [pub.dev][]. +In general, use `pubspec.yaml` to declare +external dependencies to use in Flutter. +A good place to find Flutter packages is on [pub.dev][]. ## Application lifecycle ### How do I listen to application lifecycle events? -In Xamarin.Forms, you have an `Application` that contains `OnStart`, -`OnResume` and `OnSleep`. In Flutter you can instead listen to similar -lifecycle events by hooking into the `WidgetsBinding` observer and -listening to the `didChangeAppLifecycleState()` change event. +In Xamarin.Forms, you have an `Application` +that contains `OnStart`, `OnResume` and `OnSleep`. +In Flutter, you can instead listen to similar lifecycle events +by hooking into the `WidgetsBinding` observer and listening to +the `didChangeAppLifecycleState()` change event. The observable lifecycle events are: -
-
`inactive`
-
The application is in an inactive state and is not receiving - user input. This event is iOS only.
-
`paused`
-
The application is not currently visible to - the user, is not responding to user input, - but is running in the background.
-
`resumed`
-
The application is visible and responding to user input.
-
`suspending`
-
The application is suspended momentarily. This event is Android - only.
-
- -For more details on the meaning of these states, see the -[`AppLifecycleStatus` documentation][]. +`inactive` +: The application is in an inactive state and is not receiving user input. + This event is iOS only. + +`paused` +: The application is not currently visible to the user, + is not responding to user input, but is running in the background. + +`resumed` +: The application is visible and responding to user input. + +`suspending` +: The application is suspended momentarily. + This event is Android only. + +For more details on the meaning of these states, +see the [`AppLifecycleStatus` documentation][]. [`AppLifecycleStatus` documentation]: {{site.api}}/flutter/dart-ui/AppLifecycleState.html @@ -1445,14 +1489,16 @@ For more details on the meaning of these states, see the ### What is the equivalent of a StackLayout? -In Xamarin.Forms you can create a `StackLayout` with an `Orientation` of -horizontal or vertical. Flutter has a similar approach, +In Xamarin.Forms you can create a `StackLayout` +with an `Orientation` of horizontal or vertical. +Flutter has a similar approach, however you would use the `Row` or `Column` widgets. -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. +If you notice the two code samples are identical +except 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. ```dart @@ -1460,7 +1506,7 @@ children. Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: const [ Text('Row One'), Text('Row Two'), Text('Row Three'), @@ -1476,7 +1522,7 @@ Widget build(BuildContext context) { Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: const [ Text('Column One'), Text('Column Two'), Text('Column Three'), @@ -1499,32 +1545,43 @@ GridView.count( // Create a grid with 2 columns. If you change the scrollDirection to // horizontal, this would produce 2 rows. crossAxisCount: 2, - // Generate 100 widgets that display their index in the List - children: List.generate(100, (index) { - return Center( - child: Text( - 'Item $index', - style: Theme.of(context).textTheme.headline, - ), - ); - }), + // Generate 100 widgets that display their index in the list. + children: List.generate( + 100, + (int index) { + return Center( + child: Text( + 'Item $index', + style: Theme.of(context).textTheme.headlineMedium, + ), + ); + }, + ), ); ``` -You might have used a `Grid` in Xamarin.Forms to implement widgets -that overlay other widgets. -In Flutter, you accomplish this with the `Stack` widget +You might have used a `Grid` in Xamarin.Forms +to implement widgets that overlay other widgets. +In Flutter, you accomplish this with the `Stack` widget. This sample creates two icons that overlap each other. ```dart -child: Stack( - children: [ - Icon(Icons.add_box, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)), +Stack( + children: const [ + Icon( + Icons.add_box, + size: 24.0, + color: Colors.black, + ), Positioned( left: 10.0, - child: Icon(Icons.add_circle, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)), + child: Icon( + Icons.add_circle, + size: 24.0, + color: Colors.black, + ), ), ], ), @@ -1532,8 +1589,8 @@ child: Stack( ### What is the equivalent of a ScrollView? -In Xamarin.Forms, a `ScrollView` wraps around a `VisualElement` and, -if the content is larger than the device screen, it scrolls. +In Xamarin.Forms, a `ScrollView` wraps around a `VisualElement`, +and if the content is larger than the device screen, it scrolls. In Flutter, the closest match is the `SingleChildScrollView` widget. You simply fill the Widget with the content that you want to be scrollable. @@ -1542,17 +1599,16 @@ You simply fill the Widget with the content that you want to be scrollable. ```dart @override Widget build(BuildContext context) { - return SingleChildScrollView( + return const SingleChildScrollView( child: Text('Long Content'), ); } ``` If you have many items you want to wrap in a scroll, -even of different `Widget` types, you might want -to use a `ListView`. This might seem like overkill, -but in Flutter this is far more optimized -and less intensive than a Xamarin.Forms `ListView`, +even of different `Widget` types, you might want to use a `ListView`. +This might seem like overkill, but in Flutter this is +far more optimized and less intensive than a Xamarin.Forms `ListView`, which is backing on to platform specific controls. @@ -1560,7 +1616,7 @@ which is backing on to platform specific controls. @override Widget build(BuildContext context) { return ListView( - children: [ + children: const [ Text('Row One'), Text('Row Two'), Text('Row Three'), @@ -1575,169 +1631,176 @@ Widget build(BuildContext context) { Landscape transitions can be handled automatically by setting the `configChanges` property in the AndroidManifest.xml: -```yaml -android:configChanges="orientation|screenSize" +```xml + ``` ## Gesture detection and touch event handling ### How do I add GestureRecognizers to a widget in Flutter? -In Xamarin.Forms, `Element`s might contain a click event you -can attach to. Many elements also contain a `Command` that is -tied to this event. Alternatively you would use the -`TapGestureRecognizer`. In Flutter there are two very similar ways: - - 1. If the widget supports event detection, pass a function to it and - handle it in the function. For example, the ElevatedButton has an - `onPressed` parameter: - - - ```dart - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: () { - print("click"); - }, - child: Text("Button")); - } - ``` - - 2. If the widget doesn't support event detection, wrap the - widget in a GestureDetector and pass a function to the - `onTap` parameter. - - - ```dart - class SampleApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: GestureDetector( - child: FlutterLogo( - size: 200.0, - ), - onTap: () { - print("tap"); - }, - ), - )); - } +In Xamarin.Forms, `Element`s might contain a click event you can attach to. +Many elements also contain a `Command` that is tied to this event. +Alternatively you would use the `TapGestureRecognizer`. +In Flutter there are two very similar ways: + +1. If the widget supports event detection, pass a function to it and + handle it in the function. For example, the ElevatedButton has an + `onPressed` parameter: + + + ```dart + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + print('click'); + }, + child: Text("Button"), + ); } - ``` + ``` + +2. If the widget doesn't support event detection, wrap the + widget in a `GestureDetector` and pass a function + to the `onTap` parameter. + + + ```dart + class SampleApp extends StatelessWidget { + const SampleApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: GestureDetector( + onTap: () { + print("tap"); + }, + child: const FlutterLogo(size: 200.0), + ), + ), + ); + } + } + ``` ### How do I handle other gestures on widgets? -In Xamarin.Forms you would add a `GestureRecognizer` to the -`View`. You would normally be limited to `TapGestureRecognizer`, +In Xamarin.Forms you would add a `GestureRecognizer` to the `View`. +You would normally be limited to `TapGestureRecognizer`, `PinchGestureRecognizer`, `PanGestureRecognizer`, `SwipeGestureRecognizer`, -`DragGestureRecognizer` and `DropGestureRecognizer` -unless you built your own. +`DragGestureRecognizer` and `DropGestureRecognizer` unless you built your own. -In Flutter, using the GestureDetector, you can listen to a wide -range of Gestures such as: +In Flutter, 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 the `onTapDown` won't - cause a 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 the `onTapDown` + won't cause a tap. * Double tap -
-
`onDoubleTap`
-
The user tapped the screen at the same location twice in - quick succession.
-
+`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.
-
+`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.
-
+`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 a `GestureDetector` that rotates the -Flutter logo on a double tap: +`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 a `GestureDetector` +that rotates the Flutter logo on a double tap: ```dart -AnimationController controller; -CurvedAnimation curve; +class SampleAppPage extends StatefulWidget { + const SampleAppPage({Key? key}) : super(key: key); -@override -void initState() { - super.initState(); - controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); - curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); + @override + _SampleAppPageState createState() => _SampleAppPageState(); } -class SampleApp extends StatelessWidget { +class _SampleAppPageState extends State + with SingleTickerProviderStateMixin { + late final AnimationController controller; + late final CurvedAnimation curve; + + @override + void initState() { + super.initState(); + controller = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + ); + curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); + } + @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: GestureDetector( - child: RotationTransition( - turns: curve, - child: FlutterLogo( - size: 200.0, - )), - onDoubleTap: () { - if (controller.isCompleted) { - controller.reverse(); - } else { - controller.forward(); - } - }, + body: Center( + child: GestureDetector( + onDoubleTap: () { + if (controller.isCompleted) { + controller.reverse(); + } else { + controller.forward(); + } + }, + child: RotationTransition( + turns: curve, + child: const FlutterLogo(size: 200.0), + ), ), - )); + ), + ); } } ``` @@ -1748,72 +1811,67 @@ class SampleApp extends StatelessWidget { The equivalent to a `ListView` in Flutter is … a `ListView`! -In a Xamarin.Forms `ListView`, -you create a `ViewCell` and possibly a `DataTemplateSelector` -and pass it into the `ListView`, which renders each row with -what your `DataTemplateSelector` or `ViewCell` returns. +In a Xamarin.Forms `ListView`, you create a `ViewCell` +and possibly a `DataTemplateSelector`and pass it into the `ListView`, +which renders each row with what your +`DataTemplateSelector` or `ViewCell` returns. However, you often have to make sure you turn on Cell Recycling otherwise you will run into memory issues and slow scrolling speeds. -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. +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. ```dart import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } -class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); +class SampleAppPage extends StatelessWidget { + const SampleAppPage({Key? key}) : super(key: key); - @override - _SampleAppPageState createState() => _SampleAppPageState(); -} + List _getListData() { + return List.generate( + 100, + (int index) => Padding( + padding: const EdgeInsets.all(10.0), + child: Text('Row $index'), + ), + ); + } -class _SampleAppPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), + appBar: AppBar(title: const Text('Sample App')), body: ListView(children: _getListData()), ); } - - _getListData() { - List widgets = []; - for (int i = 0; i < 100; i++) { - widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i"))); - } - return widgets; - } } ``` ### How do I know which list item has been clicked? -In Xamarin.Forms, the ListView has an `ItemTapped` method to find out -which item was clicked. There are many other techniques you might have -used such as checking when `SelectedItem` or `EventToCommand` +In Xamarin.Forms, the ListView has an `ItemTapped` method +to find out which item was clicked. +There are many other techniques you might have used +such as checking when `SelectedItem` or `EventToCommand` behaviors change. In Flutter, use the touch handling provided by the passed-in widgets. @@ -1823,76 +1881,75 @@ In Flutter, use the touch handling provided by the passed-in widgets. import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { + List _getListData() { + return List.generate( + 100, + (int index) => GestureDetector( + onTap: () { + print('Row $index tapped'); + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text('Row $index'), + ), + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), + appBar: AppBar(title: const Text('Sample App')), body: ListView(children: _getListData()), ); } - - _getListData() { - List widgets = []; - for (int i = 0; i < 100; i++) { - widgets.add(GestureDetector( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row $i")), - onTap: () { - print('row tapped'); - }, - )); - } - return widgets; - } } ``` ### How do I update a ListView dynamically? -In Xamarin.Forms, if you bound the `ItemsSource` property to -an `ObservableCollection` you would just update the list in your -ViewModel. Alternatively, you could assign a new `List` to the -`ItemSource` property. +In Xamarin.Forms, if you bound the +`ItemsSource` property to an `ObservableCollection`, +you would just update the list in your ViewModel. +Alternatively, you could assign a new `List` to the `ItemSource` property. In Flutter, things work a little differently. If you update the list of widgets inside a `setState()` method, you would quickly see that your data did not change visually. This is because when `setState()` is called, -the Flutter rendering engine looks at the widget tree to see if -anything has changed. When it gets to your -`ListView`, it performs a `==` check, and determines that the two -`ListView`s are the same. Nothing has changed, so no update is required. - -For a simple way to update your `ListView`, create a new `List` inside of -`setState()`, and copy the data from the old list to the new list. +the Flutter rendering engine looks at the widget tree +to see if anything has changed. +When it gets to your `ListView`, it performs a `==` check, +and determines that the two `ListView`s are the same. +Nothing has changed, so no update is required. + +For a simple way to update your `ListView`, +create a new `List` inside of `setState()`, +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. @@ -1901,32 +1958,31 @@ as shown in the next example. import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - List widgets = []; + List widgets = []; @override void initState() { @@ -1936,63 +1992,62 @@ class _SampleAppPageState extends State { } } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: ListView(children: widgets), - ); - } - - Widget getRow(int i) { + Widget getRow(int index) { return GestureDetector( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row $i")), onTap: () { setState(() { - widgets = List.from(widgets); + widgets = List.from(widgets); widgets.add(getRow(widgets.length)); - print('row $i'); + print('Row $index'); }); }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text('Row $index'), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sample App')), + body: ListView(children: widgets), ); } } ``` -The recommended, efficient, and effective way to build a list uses a -ListView.Builder. This method is great when you have a dynamic -list or a list with very large amounts of data. This is essentially -the equivalent of RecyclerView on Android, which automatically -recycles list elements for you: +The recommended, efficient, and effective way to build a list +uses a `ListView.Builder`. +This method is great when you have a dynamic list +or a list with very large amounts of data. +This is essentially the equivalent of RecyclerView on Android, +which automatically recycles list elements for you: ```dart import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); @@ -2009,41 +2064,42 @@ class _SampleAppPageState extends State { } } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: ListView.builder( - itemCount: widgets.length, - itemBuilder: (BuildContext context, int position) { - return getRow(position); - })); - } - - Widget getRow(int i) { + Widget getRow(int index) { return GestureDetector( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Text("Row $i")), onTap: () { setState(() { widgets.add(getRow(widgets.length)); - print('row $i'); + print('Row $index'); }); }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text('Row $index'), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sample App')), + body: ListView.builder( + itemCount: widgets.length, + itemBuilder: (BuildContext context, int index) { + return getRow(index); + }, + ), ); } } ``` -Instead of creating a "ListView", create a ListView.builder that -takes two key parameters: the initial length of the list, -and an ItemBuilder function. +Instead of creating a `ListView`, create a `ListView.builder` +that takes two key parameters: the initial length of the list, +and an item builder function. -The ItemBuilder function is similar to the `getView` function in -an Android adapter; it takes a position, +The item builder function is similar to the `getView` 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 the `onTap()` function @@ -2057,20 +2113,20 @@ and [Write your first Flutter app, part 2][]. ### How do I set custom fonts on my text widgets? -In Xamarin.Forms, you would have to add a custom font in each native -project. Then, in your `Element` you would assign this font name +In Xamarin.Forms, you would have to add a custom font in each native project. +Then, in your `Element` you would assign this font name to the `FontFamily` attribute using `filename#fontname` and just `fontname` for iOS. -In Flutter, place the font file in a folder and reference it in the -`pubspec.yaml` file, similar to how you import images. +In Flutter, place the font file in a folder and reference it +in the `pubspec.yaml` file, similar to how you import images. ```yaml fonts: - - family: MyCustomFont - fonts: - - asset: fonts/MyCustomFont.ttf - - style: italic + - family: MyCustomFont + fonts: + - asset: fonts/MyCustomFont.ttf + - style: italic ``` Then assign the font to your `Text` widget: @@ -2080,10 +2136,8 @@ Then assign the font to your `Text` widget: @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), - body: Center( + appBar: AppBar(title: const Text('Sample App')), + body: const Center( child: Text( 'This is a custom font text', style: TextStyle(fontFamily: 'MyCustomFont'), @@ -2119,20 +2173,21 @@ where you can customize many parameters, such as: ### How do I retrieve user input? Xamarin.Forms `element`s allow you to directly query the `element` -to determine the state of any of its properties, +to determine the state of its properties, or whether it's bound to a property in a `ViewModel`. Retrieving information in Flutter is handled by specialized widgets -and is different than how you are used to. If you have a `TextField` -or a `TextFormField`, you can supply a [`TextEditingController`][] +and is different from how you are used to. +If you have a `TextField`or a `TextFormField`, +you can supply a [`TextEditingController`][] to retrieve user input: ```dart class _MyFormState extends State { - // Create a text controller and use it to retrieve the current value - // of the TextField. - final myController = TextEditingController(); + /// Create a text controller and use it to retrieve the current value + /// of the TextField. + final TextEditingController myController = TextEditingController(); @override void dispose() { @@ -2144,14 +2199,10 @@ class _MyFormState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('Retrieve Text Input'), - ), + appBar: AppBar(title: const Text('Retrieve Text Input')), body: Padding( padding: const EdgeInsets.all(16.0), - child: TextField( - controller: myController, - ), + child: TextField(controller: myController), ), floatingActionButton: FloatingActionButton( // When the user presses the button, show an alert dialog with the @@ -2159,7 +2210,7 @@ class _MyFormState extends State { onPressed: () { return showDialog( context: context, - builder: (context) { + builder: (BuildContext context) { return AlertDialog( // Retrieve the text that the user has entered using the // TextEditingController. @@ -2169,7 +2220,7 @@ class _MyFormState extends State { ); }, tooltip: 'Show me the value!', - child: Icon(Icons.text_fields), + child: const Icon(Icons.text_fields), ), ); } @@ -2190,16 +2241,16 @@ that you can assign a value to. For example: ``` 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. +for your input by adding an `InputDecoration` object +to the `decoration` constructor parameter for the text widget. ```dart -body: Center( +body: const Center( child: TextField( - decoration: InputDecoration(hintText: "This is a hint"), - ) -) + decoration: InputDecoration(hintText: 'This is a hint'), + ), +), ``` ### How do I show validation errors? @@ -2220,39 +2271,49 @@ update the state, and pass a new `InputDecoration` object. import 'package:flutter/material.dart'; void main() { - runApp(SampleApp()); + runApp(const SampleApp()); } class SampleApp extends StatelessWidget { - // This widget is the root of your application. + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Sample App', - theme: ThemeData( - primarySwatch: Colors.blue, - ), home: SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { - SampleAppPage({Key key}) : super(key: key); + const SampleAppPage({Key? key}) : super(key: key); @override _SampleAppPageState createState() => _SampleAppPageState(); } class _SampleAppPageState extends State { - String _errorText; + String? _errorText; + + String? _getErrorText() { + return _errorText; + } + + bool isEmail(String em) { + const String emailRegexp = + 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,}))$'; + final RegExp regExp = RegExp(emailRegexp); + return regExp.hasMatch(em); + } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Sample App"), - ), + appBar: AppBar(title: const Text('Sample App')), body: Center( child: TextField( onSubmitted: (String text) { @@ -2264,24 +2325,14 @@ class _SampleAppPageState extends State { } }); }, - decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()), + decoration: InputDecoration( + hintText: 'This is a hint', + errorText: _getErrorText(), + ), ), ), ); } - - _getErrorText() { - return _errorText; - } - - bool isEmail(String em) { - String emailRegexp = - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; - - RegExp regExp = RegExp(emailRegexp); - - return regExp.hasMatch(em); - } } ``` @@ -2294,19 +2345,20 @@ class _SampleAppPageState extends State { Flutter doesn't run code directly on the underlying platform; rather, the Dart code that makes up a Flutter app is run natively on the device, "sidestepping" the SDK provided by the platform. -That means, for example, when you perform a network request -in Dart, it runs directly in the Dart context. You don't use the -Android or iOS APIs you normally take advantage of when writing -native apps. Your Flutter app is still hosted in a native app's +That means, for example, when you perform a network request in Dart, +it runs directly in the Dart context. +You don't use the Android or iOS APIs +you normally take advantage of when writing native apps. +Your Flutter app is still hosted in a native app's `ViewController` or `Activity` as a view, but you don't have direct access to this, or the native framework. This doesn't mean Flutter apps can't interact with those native APIs, -or with any native code you have. Flutter provides -[platform channels][] that communicate and exchange data with -the `ViewController` or `Activity` that hosts your Flutter view. -Platform channels are essentially an asynchronous messaging -mechanism that bridges the Dart code with the host `ViewController` +or with any native code you have. Flutter provides [platform channels][] +that communicate and exchange data with the +`ViewController` or `Activity` that hosts your Flutter view. +Platform channels are essentially an asynchronous messaging mechanism +that bridges the Dart code with the host `ViewController` or `Activity` and the iOS or Android framework it runs on. You can use platform channels to execute a method on the native side, or to retrieve some data from the device's sensors, for example. @@ -2331,8 +2383,7 @@ Use the [`geolocator`][] community plugin. ### How do I access the camera? -The [`image_picker`][] plugin is popular -for accessing the camera. +The [`camera`][] plugin is popular for accessing the camera. ### How do I log in with Facebook? @@ -2359,8 +2410,9 @@ 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 the +If there is platform-specific functionality that Flutter +or its community plugins are missing, +you can build your own following the [developing packages and plugins][] page. Flutter's plugin architecture, in a nutshell, @@ -2373,23 +2425,23 @@ on Android or iOS. ### How do I theme my app? -Flutter comes with a beautiful, built-in implementation of Material -Design, which handles much of the styling and theming needs that you would -typically do. +Flutter comes with a beautiful, built-in implementation of Material Design, +which handles much of the styling and theming needs +that you would typically do. -Xamarin.Forms does have a global `ResourceDictionary` where you can -share styles across your app. Alternatively, there is Theme support -currently in preview. +Xamarin.Forms does have a global `ResourceDictionary` +where you can share styles across your app. +Alternatively, there is Theme support currently in preview. -In Flutter you declare themes in the top level widget. +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 widget `MaterialApp` 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 declare a top level widget `MaterialApp` +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 a `WidgetsApp` as your app widget, which provides some of the same functionality, @@ -2403,15 +2455,18 @@ the primary swatch is set to blue and text selection color is red. ```dart class SampleApp extends StatelessWidget { + /// This widget is the root of your application. + const SampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( primarySwatch: Colors.blue, - textSelectionColor: Colors.red + textSelectionColor: Colors.red, ), - home: SampleAppPage(), + home: const SampleAppPage(), ); } } @@ -2445,19 +2500,19 @@ Use the [DevTools][] 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, see the -[DevTools][] documentation. +observing executed lines of code, +debugging memory leaks and memory fragmentation. +For more information, see the [DevTools][] documentation. ## Notifications ### How do I set up push notifications? -In Android, you use Firebase Cloud Messaging to setup push -notifications for your app. +In Android, you use Firebase Cloud Messaging to setup +push notifications for your app. In Flutter, access this functionality using the -[Firebase_Messaging][] plugin. +[`firebase_messaging`][] plugin. For more information on using the Firebase Cloud Messaging API, see the [`firebase_messaging`][] plugin documentation. @@ -2482,14 +2537,13 @@ For more information on using the Firebase Cloud Messaging API, see the [`firebase_database`]: {{site.pub}}/packages/firebase_database [`firebase_messaging`]: {{site.pub}}/packages/firebase_messaging [`firebase_storage`]: {{site.pub}}/packages/firebase_storage -[Firebase_Messaging]: {{site.pub}}/packages/firebase_messaging [first party plugins]: {{site.pub}}/flutter/packages?q=firebase [Flutter cookbook]: {{site.url}}/cookbook [`flutter_facebook_login`]: {{site.pub}}/packages/flutter_facebook_login [`flutter_firebase_ui`]: {{site.pub}}/packages/flutter_firebase_ui [`geolocator`]: {{site.pub}}/packages/geolocator +[`camera`]: {{site.pub-pkg}}/camera [`http` package]: {{site.pub}}/packages/http -[`image_picker`]: {{site.pub}}/packages/image_picker [internationalization guide]: {{site.url}}/development/accessibility-and-localization/internationalization [`intl`]: {{site.pub}}/packages/intl [`intl_translation`]: {{site.pub}}/packages/intl_translation