How to do this in Flutter?

Cheat sheet for react native developers.
Find Flutter alternatives to familiar concepts

Many examples are not really specific to React Native or Flutter but rather JavaScript and Dart

You might also want to check out flutter for react native devs

Init

React Native

react-native init MyProject

Flutter

flutter create my_project

Healthcheck

React Native

Is there a way to verify react-native installation is complete and working? Let me know

Flutter

flutter doctor

Hello World

React Native

import React, { Component } from 'react';
import { Text, View, AppRegistry } from 'react-native';
import { name as appName } from './app.json';

export default class HelloWorldApp extends Component {
  render() {
    return (
      <View>
        <Text>Hello world!</Text>
      </View>
    );
  }
}

AppRegistry.registerComponent(appName, () => App);

Flutter

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hello world!',
      home: Scaffold(
        body: Center(
          child: Text('Hello world'),
        ),
      ),
    );
  }
}

Stateless Component

React Native

import React from 'react';

type Props = {
  name: string,
};

const Greeter = props => (
  <View>
    <Text>Hello, {props.name}</Text>
  </View>
);

Flutter

import 'package:flutter/material.dart';

class Greeter extends StatelessWidget {
  Greeter({ this.name});

  String name;

  
  Widget build(BuildContext context) {
    return Container(
      child: Text('Hello, $name'),
    );
  }
}

Required and default props

React Native

import React from 'react';

type Props = {
  foo: string,
  bar?: string,
};

const SomeComponent = (props: Props) => (
  <View>
    <Text>{`${props.foo} ${props.bar}`}</Text>
  </View>
);

SomeComponent.defaultProps = {
  bar: 'some string',
};

Flutter

import 'package:flutter/material.dart';

class SomeComponent extends StatelessWidget {
  SomeComponent({
     this.foo,
    this.bar = 'some string',
  });

  final String foo;
  final String bar;

  
  Widget build(BuildContext context) {
    return Container(
      child: Text('$foo $bar'),
    );
  }
}

Stateful component

React Native

import React from 'react';
import { Button } from 'react-native';

type State = {
  counter: number,
};

class ComponentWithState extends React.Component<{}, State> {
  state = {
    counter: 0,
  };

  increment = () => {
    this.setState({
      counter: this.state.counter + 1,
    });
  };

  decrement = () => {
    this.setState({
      counter: this.state.counter - 1,
    });
  };

  render() {
    return (
      <View>
        <Button onPress={this.increment} title="Increment" />
        <Button onPress={this.decrement} title="Decrement" />
        <Text>{this.state.counter}</Text>
      </View>
    );
  }
}

Flutter

import 'package:flutter/material.dart';

class WidgetWithState extends StatefulWidget {
  
  _WidgetWithStateState createState() => _WidgetWithStateState();
}

class _WidgetWithStateState extends State<WidgetWithState> {
  int counter = 0;

  increment() {
    setState(() {
      counter++;
    });
  }

  decrement() {
    setState(() {
      counter--;
    });
  }

  
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        FlatButton(onPressed: increment, child: Text('Increment')),
        FlatButton(onPressed: decrement, child: Text('Decrement')),
        Text(counter.toString()),
      ],
    );
  }
}

Combining props and state

React Native

import React from 'react';

type Props = {
  fruit: string,
};

type State = {
  count: number,
};

class SomeComponent extends React.Component<Props, State> {
  state = {
    count: 0,
  };

  render() {
    return (
      <View>
        <Text>
          {this.state.count} {this.prop.fruit}
        </Text>
      </View>
    );
  }
}

const ParentComponent = () => (
  <View>
    <SomeComponent fruit="oranges" />
  </View>
);

Flutter

import 'package:flutter/material.dart';

class SomeWidget extends StatefulWidget {
  SomeWidget({ this.fruit});

  final String fruit;

  
  _SomeWidgetState createState() => _SomeWidgetState();
}

class _SomeWidgetState extends State<SomeWidget> {
  int count = 0;

  
  Widget build(BuildContext context) {
    return Container(
      child: Text('$count ${widget.fruit}'),
    );
  }
}

class ParentWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Container(
        child: SomeWidget(fruit: 'oranges'),
    );
  }
}

Lifecycle hooks

There are some similarities between lifecycle hooks in react native and Flutter, but do not treat them as 100% the same thing. Find more info here:

React Native

class MyComponent extends React.Component<{}, {}> {
  //
  componentDidMount() {
    //
    // some code here
    //
  }

  componentDidUpdate(prevProps, prevState) {
    //
    // some code here
    //
    //
  }

  //
  componentWillUnmount() {
    // some code here
    //
  }
}

Flutter

class _MyComponentState extends State<MyComponent> {
  
  void initState() {
    // unlike React this method is called _before_ the build
    super.initState();
  }

  
  void didUpdateWidget(MyComponent oldWidget) {
    // this method IS called when parent widget passes new "props"
    // unlike React, this method IS called _before_ the build
    // unlike React, this method ISN'T called after setState()
    super.didUpdateWidget(oldWidget);
  }

  
  void dispose() {
    // some code here
    super.dispose();
  }
}

TouchableNativeFeedback

React Native

<TouchableNativeFeedback
  onPress={_onPress}
  onLongPress={_onLongPress}
>
  <Text>Button</Text>
</TouchableNativeFeedback>

Flutter

InkWell(
  child: Text('Button'),
  onTap: _onTap,
  onLongPress: _onLongPress,
  onDoubleTap: _onDoubleTap,
  onTapCancel: _onTapCancel,
);

TouchableWithoutFeedback

React Native

<TouchableWithoutFeedback
  onPress={_onPress}
  onLongPress={_onLongPress}
>
  <Text>Button</Text>
</TouchableWithoutFeedback>

Flutter

GestureDetector(
  onTap: _onTap,
  onLongPress: _onLongPress,
  child: Text('Button'),
);

Loading indicator while something is loading

React Native

import React from 'react';
import { View, Text, ActivityIndicator } from 'react-native';

type State = {
  isLoading: boolean,
  data: number,
};

class SomeComponent extends React.PureComponent<{}, State> {
  state = {
    isLoading: true,
    data: null,
  };

  componentDidMount() {
    Promise.resolve(42).then(data => {
      this.setState({
        data,
        isLoading: false,
      });
    });
  }

  render() {
    const content = this.state.isLoading ? (
      <ActivityIndicator size="large" color="#0000ff" />
    ) : (
      <Text>{`${42}`}</Text>
    );

    return <View>{content}</View>;
  }
}

Flutter

class SomeWidget extends StatefulWidget {
  
  _SomeWidgetState createState() => _SomeWidgetState();
}

class _SomeWidgetState extends State<SomeWidget> {
  Future<int> future;

  
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        return snapshot.hasData
            ? Text('${snapshot.data}')
            : CircularProgressIndicator();
      },
    );
  }
}

Platform specific code

React Native

import { Platform } from 'react-native';

if (Platform.OS === 'ios') {
  doSmthIOSSpecific();
}

if (Platform.OS === 'android') {
  doSmthAndroidSpecific();
}

Flutter

import 'dart:io' show Platform;

if (Platform.isIOS) {
  doSmthIOSSpecific();
}

if (Platform.isAndroid) {
  doSmthAndroidSpecific();
}

Hide status bar

React Native

import React from 'react';
import { StatusBar } from 'react-native';

const SomeComponent = () => (
  <View>
    <StatusBar hidden={true} />
  </View>
);

Flutter

import 'package:flutter/services.dart';

void main() {
    SystemChrome.setEnabledSystemUIOverlays([]);
}

Lock orientation

React Native

There is no way to lock device orientation directly from react-native, so you should do this in native android and iOS config or with help of this module

Flutter

import 'package:flutter/services.dart';

void main() async {
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);

  runApp(App());
}

Show alert

React Native

Alert.alert(
  'Alert Title',
  'My Alert Msg',
  [
    {
      text: 'Ask me later',
      onPress: () => {
          console.log('Ask me later pressed'),
      }
    },
    {
      text: 'Cancel',
      onPress: () => {
          console.log('Cancel Pressed'),
      }
      style: 'cancel',
    },
    {
        text: 'OK',
        onPress: () => {
            console.log('OK Pressed');
        },
    },
  ],
  { cancelable: false }
);

Flutter

showDialog<void>(
  context: context,
  barrierDismissible: false,
  builder: (BuildContext context) {
    return AlertDialog(
      title: Text('Alert Title'),
      content: Text('My Alert Msg'),
      actions: <Widget>[
        FlatButton(
          child: Text('Ask me later'),
          onPressed: () {
            print('Ask me later pressed');
            Navigator.of(context).pop();
          },
        ),
        FlatButton(
          child: Text('Cancel'),
          onPressed: () {
            print('Cancel pressed');
            Navigator.of(context).pop();
          },
        ),
        FlatButton(
          child: Text('OK'),
          onPressed: () {
            print('OK pressed');
            Navigator.of(context).pop();
          },
        ),
      ],
    );
  },
);

Check if dev

React Native

if (__DEV__) {
  doSmth();
}

Flutter

bool isDev = false;
assert(isDev = true);

if (isDev) {
    doSmth();
}

React Native

npm i react-navigation react-native-gesture-handler --save
import React from 'react';
import { Button, View } from 'react-native';
import {
  createAppContainer,
  createStackNavigator,
} from 'react-navigation';

const FirstScreen = props => (
  <View>
    <Button
      title="Go to SecondScreen"
      onPress={() => props.navigation.navigate('Second')}
    />
  </View>
);

const SecondScreen = props => (
  <View>
    <Button title="Go back!" onPress={props.navigation.goBack} />

    <Button
      title="Go to SecondScreen... again"
      onPress={() => props.navigation.push('Second')}
    />
  </View>
);

const RootStack = createStackNavigator(
  {
    First: { screen: FirstScreen },
    Second: { screen: SecondScreen },
  },
  {
    initialRouteName: 'First',
  }
);

const AppContainer = createAppContainer(RootStack);
const App = () => <AppContainer />;

Flutter

import 'package:flutter/material.dart';

class FirstScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        child: Text('Go to SecondScreen'),
        onPressed: () => Navigator.pushNamed(context, '/second'),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  void _pushSecondScreen(context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) => SecondScreen()));
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        RaisedButton(
          child: Text('Go back!'),
          onPressed: () => Navigator.pop(context),
        ),
        RaisedButton(
          child: Text('Go to SecondScreen... again!'),
          onPressed: () => _pushSecondScreen(context),
        ),
      ],
    );
  }
}

void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) => FirstScreen(),
      '/second': (context) => SecondScreen(),
    },
  ));
}

Arrays

React Native

const length = items.length;

const newItems = items.concat(otherItems);

const allEven = items.every(item => item % 2 == 0);

const filled = Array(3).fill(42);

const even = items.filter(n => n % 2 === 0);

const found = items.find(item => item.id == 42);

const index = items.findIndex(item => item.id == 42);

const flat = items.flat();

const mapped = items.flatMap(item => item + 1);

items.forEach(item => console.log(item));

items.forEach((item, index) => console.log(item, index));

const includes = items.includes(42);

const indexOf = items.indexOf(item => item === 42);

const joined = items.join(',');

const newItems = items.map(item => item + 1);

const item = items.pop();

items.push(42);

const reduced = items.reduce((acc, item) => {
  acc[item.id] = item;
  return acc;
}, {});

const reversed = items.reverse();

items.shift();

const slice = items.slice(15, 42);

const hasOdd = items.some(item => item % 2);

items.sort((a, b) => a - b);

items.splice(15, 42, [1, 2, 3]);

items.unshift(42);

Flutter

final length = items.length;

final newItems = items..addAll(otherItems);

final allEven = items.every((item) => item % 2 == 0);

final filled = List<int>.filled(3, 42);

final even = items.where((n) => n % 2 == 0).toList();

final found = items.firstWhere((item) => item.id == 42);

final index = items.indexWhere((item) => item.id == 42);

final flat = items.expand((_) => _).toList();

final mapped = items.expand((item) => [item + 1]).toList();

items.forEach((item) => print(item));

items.asMap().forEach((index, item) => print('$item, $index'));

final includes = items.contains(42);

final indexOf = items.indexOf(42);

final joined = items.join(',');

final newItems = items.map((item) => item + 1).toList();

final item = items.removeLast();

items.add(42);

final reduced = items.fold({}, (acc, item) {
  acc[item.id] = item;
  return acc;
});

final reversed = items.reversed;

items.removeAt(0);

final slice = items.sublist(15, 42);

final hasOdd = items.any((item) => item % 2 == 0);

items.sort((a, b) => a - b);

items.replaceRange(15, 42, [1, 2, 3]);

items.insert(0, 42);

Fetch

React Native

fetch(API_URL)
  .then(res => res.json())
  .then(console.log);

Flutter

dependencies:
  http: ^0.12.0
import 'dart:convert' show json;
import 'package:http/http.dart' as http;

http.get(API_URL).then((http.Response res) {
    final data = json.decode(res.body);
    print(data);
});

Async Await

React Native

async function doSmthAsync() {
  const result = await Promise.resolve(42);
  return result;
}

class SomeClass {
  async method() {
    const result = await Promise.resolve(42);
    return result;
  }
}

Flutter

Future<int> doSmthAsync() async {
  final result = await Future.value(42);
  return result;
}

class SomeClass {
  method() async {
    final result = await Future.value(42);
    return result;
  }
}

JSON

React Native

JSON.parse(someString);
JSON.stringify(someString);

Flutter

import 'dart:convert' show json;

json.decode(someString);
json.encode(encodableObject);

json.decode returns a dynamic type, which is probably not very useful

You should describe each entity as a Dart class with fromJson and toJson methods

class User {
    String displayName;
    String photoUrl;

    User({this.displayName, this.photoUrl});

    User.fromJson(Map<String, dynamic> json)
      : displayName = json['displayName'],
        photoUrl = json['photoUrl'];

    Map<String, dynamic> toJson() {
      return {
        'displayName': displayName,
        'photoUrl': photoUrl,
      };
    }
}

final user = User.fromJson(json.decode(jsonString));
json.encode(user.toJson());

However this approach is error-prone (e.g. you can forget to update map key after class field was renamed), so you can use json_serializable as an alternative

Add json_annotation, build_runner and json_serializable to dependencies

dependencies:
  json_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

Update your code

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

()
class User {
  String displayName;
  String photoUrl;

  User({this.displayName this.photoUrl});

  // _$UserFromJson is generated and available in user.g.dart
  factory User.fromJson(Map<String, dynamic> json) {
    return _$UserFromJson(json);
  }

  // _$UserToJson is generated and available in user.g.dart
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

final user = User.fromJson(json.decode(jsonString));
json.encode(user); // toJson is called by encode

Run flutter packages pub run build_runner build to generate serialization/deserialization code

To watch for changes run flutter packages pub run build_runner watch

Read more about json and serialization here

Singleton

React Native

class Singleton {
  static _instance;

  constructor() {
    if (Singleton._instance) {
      return Singleton._instance;
    }

    this.prop = 42;

    Singleton._instance = this;
  }
}

Flutter

class Singleton {
  static Singleton _instance;

  final int prop;

  factory Singleton() =>
    _instance ??= new Singleton._internal();

  Singleton._internal()
    : prop = 42;
}

Debounce

React Native

npm i lodash.debounce --save
import debounce from 'lodash.debounce';

debounce(someFN, 500);

Flutter

Timer _debounce;

if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
    someFN();
});
ToC