Views

Domains: Flutter

What is the equivalent of a UIView in Flutter?

How is react-style, or declarative, programming different than the traditional imperative style? For a comparison, see Introduction to declarative UI.

On iOS, most of what you create in the UI is done using view objects, which are instances of the UIView class. These can act as containers for other UIView classes, which form your layout.

In Flutter, the rough equivalent to a UIView is a Widget. Widgets don’t map exactly to iOS views, but while you’re getting acquainted with how Flutter works you can think of them as “the way you declare and construct UI”.

However, these have a few differences to a UIView. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter’s framework creates a new tree of widget instances. In comparison, an iOS view is not recreated when it changes, but rather it’s a mutable entity that is drawn once and doesn’t redraw until it is invalidated using setNeedsDisplay().

Furthermore, unlike UIView, Flutter’s widgets are lightweight, in part due to their immutability. Because they aren’t views themselves, and aren’t directly drawing anything, but rather are a description of the UI and its semantics that get “inflated” into actual view objects under the hood.

Flutter includes 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.

But Flutter is flexible and expressive enough to implement any design language. 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?

To update your views on iOS, you directly mutate them. In Flutter, widgets are immutable and not updated directly. Instead, you have to manipulate the widget’s state.

This is where the concept of Stateful vs Stateless widgets comes in. A StatelessWidget is just what it sounds like—a widget with no state attached.

StatelessWidgets are useful when the part of the user interface you are describing does not depend on anything other than the initial configuration information in the widget.

For example, in iOS, this is similar to placing a UIImageView with your logo as the image. If the logo is not changing during runtime, use a StatelessWidget in Flutter.

If you want to dynamically change the UI based on data received after making an HTTP call, use a StatefulWidget. After the HTTP call has completed, tell the Flutter framework that the widget’s State is updated, so it can update the UI.

The important difference between stateless and stateful widgets is that StatefulWidgets have a State object that stores state data and carries it over across tree rebuilds, so it’s not lost.

If you are in doubt, remember this rule: if a widget changes outside of the build method (because of runtime user interactions, for example), it’s stateful. If the widget never changes, once built, it’s stateless. However, even if a widget is stateful, the containing parent widget can still be stateless if it isn’t itself reacting to those changes (or other inputs).

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.

Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

If you look at the code above, you might notice that the Text widget carries no explicit state 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?

To achieve this, wrap the Text widget in a StatefulWidget and update it when the user clicks the button.

For example:

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = "I Like Flutter";
  void _updateText() {
    setState(() {
      // update the text
      textToShow = "Flutter is Awesome!";
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: Icon(Icons.update),
      ),
    );
  }
}

How do I lay out my widgets? Where is my Storyboard?

In iOS, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree.

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"),
    ),
    body: Center(
      child: CupertinoButton(
        onPressed: () {
          setState(() { _pressedCount += 1; });
        },
        child: Text('Hello'),
        padding: EdgeInsets.only(left: 10.0, right: 10.0),
      ),
    ),
  );
}

You can add padding to any widget, which mimics the functionality of constraints in iOS.

You can view the layouts that Flutter has to offer in the widget catalog.

How do I add or remove a component from my layout?

In iOS, you call addSubview() on the parent, or removeFromSuperview() on a child view to dynamically add or remove child views. In Flutter, because widgets are immutable there is no direct equivalent to addSubview(). 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:

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  _getToggleChild() {
    if (toggle) {
      return Text('Toggle One');
    } else {
      return CupertinoButton(
        onPressed: () {},
        child: Text('Toggle Two'),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: Icon(Icons.update),
      ),
    );
  }
}

How do I animate a widget?

In iOS, you create an animation by calling the animate(withDuration:animations:) method on a view. In Flutter, use the animation library to wrap widgets inside an animated widget.

In Flutter, use an AnimationController, which is an Animation<double> 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 Animations 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, and tell the controller to start the animation.

The following example shows how to write a FadeTransition that fades the widget into a logo when you press the FloatingActionButton:

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return 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);

  final String title;

  @override
  _MyFadeTest createState() => _MyFadeTest();
}

class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  AnimationController controller;
  CurvedAnimation curve;

  @override
  void initState() {
    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),
      ),
      body: Center(
        child: Container(
          child: FadeTransition(
            opacity: curve,
            child: FlutterLogo(
              size: 100.0,
            )
          )
        )
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Fade',
        child: Icon(Icons.brush),
        onPressed: () {
          controller.forward();
        },
      ),
    );
  }

  @override
  dispose() {
    controller.dispose();
    super.dispose();
  }
}

For more information, see Animation & Motion widgets, the Animations tutorial, and the Animations overview.

How do I draw to the screen?

On iOS, you use CoreGraphics to draw lines and shapes to the screen. Flutter has a different API based on the Canvas class, with two other classes that help you draw: CustomPaint and CustomPainter, the latter of which implements your algorithm to draw to the canvas.

To learn how to implement a signature painter in Flutter, see Collin’s answer on StackOverflow.

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);

  final List<Offset> points;

  void paint(Canvas canvas, Size size) {
    var 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);
    }
  }

  bool shouldRepaint(SignaturePainter other) => other.points != points;
}

class Signature extends StatefulWidget {
  SignatureState createState() => SignatureState();
}

class SignatureState extends State<Signature> {

  List<Offset> _points = <Offset>[];

  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);
        });
      },
      onPanEnd: (DragEndDetails details) => _points.add(null),
      child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
    );
  }
}

Where is the widget’s opacity?

On iOS, everything has .opacity or .alpha. In Flutter, most of the time you need to wrap a widget in an Opacity widget to accomplish this.

How do I build custom widgets?

In iOS, you typically subclass UIView, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by composing smaller widgets (instead of extending them).

For example, how do you build a CustomButton that takes a label in the constructor? Create a CustomButton that composes a RaisedButton with a label, rather than by extending RaisedButton:

class CustomButton extends StatelessWidget {
  final String label;

  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(onPressed: () {}, child: Text(label));
  }
}

Then use CustomButton, just as you’d use any other Flutter widget:

@override
Widget build(BuildContext context) {
  return Center(
    child: CustomButton("Hello"),
  );
}

Similar pages

Page structure
Terms

Widget

Flutter

State

StatelessWidget

Animation

StatefulWidget

Ticker

TextStyle

Layouts

Container