Flutter most closely resembles React Native - allow for a reactive and declarative style of programming.
Unlike React Native, Flutter doesn’t need to use a JavaScript bridge, which improves app startup times and overall performance.
Dart achieves this by using Ahead-Of-Time (AOT) compilation.
Dart can also use Just-In-Time (JIT) compilation. JIT compilation with Flutter improves the development workflow by allowing a hot reload capability to refresh the UI during development.
A basic white screen:
import 'package:flutter/material.dart';
void main() => runApp(const GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
const GHFlutterApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GHFlutter',
home: Scaffold(
appBar: AppBar(
title: const Text('GHFlutter'),
),
body: const Center(
child: Text('GHFlutter'),
),
),
);
}
}
Almost every element of your Flutter app is a widget. Think of widgets like blueprints that tell how the UI should look.
Widgets are designed to be immutable, or unchangeable, since using immutable widgets helps keep the app UI lightweight.
Two fundamental types of widgets:
- Stateless: Widgets that depend only upon their own configuration info, such as a static image in an image view.
- Stateful: Widgets that need to maintain dynamic information. They do so by interacting with a State object.
To make your own widgets, go to the bottom of main.dart
and start typing stful
, an abbreviation for “stateful”.
class GHFlutter extends StatefulWidget {
const GHFlutter({ Key? key }) : super(key: key);
@override
_GHFlutterState createState() => _GHFlutterState();
}
class _GHFlutterState extends State<GHFlutter> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(strings.appTitle),
),
body: const Text(strings.appTitle),
);
}
A Scaffold is a container for Material Design widgets. It acts as the root of a widget hierarchy.
The keyword const
indicates a compile-time constant and using it allows Flutter to make optimizations.
Add two new imports at the top of main.dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
Navigate to pubspec.yaml
in the root folder of your project. In the dependencies section, add the following line (pay attention to the indentation):
http: ^0.13.3
Dart apps are single-threaded, but Dart provides support for running code on other threads that doesn’t block the UI thread with the async/await pattern.
Note: The code inside of async methods all runs on the UI thread. It’s scheduled to run later, when the UI isn’t busy. If you want to run some code on another thread, then you’d need to create a new Dart isolate.
To make the asynchronous HTTP call, add _loadData
:
var _members = <dynamic>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
Future<void> _loadData() async {
const dataUrl = 'https://api.github.com/orgs/raywenderlich/members';
final response = await http.get(Uri.parse(dataUrl));
setState(() {
_members = json.decode(response.body) as List;
});
}
Add an initState
override to the stateful class:
@override
void initState() {
super.initState();
_loadData();
}
Dart provides a ListView that lets you show the data in a list. ListView
acts like a RecyclerView
on Android or a UICollectionView
on iOS.
Add _buildRow
:
Widget _buildRow(int i) {
return ListTile(
title: Text('${_members[i]['login']}', style: _biggerFont),
);
}
Replace the body
line in the build
method:
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: _members.length,
itemBuilder: (BuildContext context, int position) {
return _buildRow(position);
}),
To add dividers into the list, use ListView.separated
instead of ListView.builder
:
body: ListView.separated(
itemCount: _members.length,
itemBuilder: (BuildContext context, int position) {
return _buildRow(position);
},
separatorBuilder: (context, index) {
return const Divider();
}),
To add padding to each row, wrap ListTile
with Padding
inside _buildRow
:
Widget _buildRow(int i) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ListTile(
title: Text('${_members[i]['login']}', style: _biggerFont),
),
);
}
Add a new Member
type at the bottom of main.dart
:
class Member {
Member(this.login);
final String login;
}
Update the _members
declaration so that it’s a list of Member
objects, then parse:
final _members = <Member>[];
setState(() {
final dataList = json.decode(response.body) as List;
for (final item in dataList) {
final login = item['login'] as String? ?? '';
final member = Member(login);
_members.add(member);
}
});
Replace the title
line of ListTile
with the following:
title: Text('${_members[i].login}', style: _biggerFont),
Update Member
to add an avatarUrl
property. Then parse:
class Member {
Member(this.login, this.avatarUrl);
final String login;
final String avatarUrl;
}
setState(() {
final dataList = json.decode(response.body) as List;
for (final item in dataList) {
final login = item['login'] as String? ?? '';
final url = item['avatar_url'] as String? ?? '';
final member = Member(login, url);
_members.add(member);
}
});
Add it to your ListTile
:
Widget _buildRow(int i) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ListTile(
title: Text('${_members[i].login}', style: _biggerFont),
leading: CircleAvatar(
backgroundColor: Colors.green,
backgroundImage: NetworkImage(_members[i].avatarUrl),
),
),
);
}
In main.dart
, add the ThemeData
to MaterialApp
widget:
import 'package:flutter/material.dart';
import 'ghflutter.dart';
import 'strings.dart' as strings;
void main() => runApp(const GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
const GHFlutterApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: strings.appTitle,
theme: ThemeData(primaryColor: Colors.green.shade800),
home: const GHFlutter(),
);
}
}