Flutter Challenge Whatsapp

Flutter Challenge Whatsapp

三月 01, 2019



Flutter Challenges will attempt to recreate a particular app UI or design in
Flutter.

This challengs will attempt the home screen of the Whatsapp Android app.
Note that the focus will be on the UI rather than actually fetching messages.

Getting Started

The WhatsApp home screen consists of

  1. An AppBar with a search action and an menu
  2. Four tabs in the bottom of the AppBar
  3. A camera tab to take a photo
  4. A FloatingActionButton for multiple purposes
  5. A “Chats” tab to view all conversations
  6. A “Status” tab to view all statuses
  7. A “Calls” tab to view all past calls

Setting up the Project

Let’s make a Flutter project named whatsapp_ui and remove all the default code
leaving just a blank screen with the default app bar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Challenge WhatsApp',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WhatsApp'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[],
),
));
}
}

The AppBar

The AppBar has the title of the app,and the two actions:Search and a menu.

Adding that to the AppBar,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AppBar buildAppBar() {
return AppBar(
title: Text(
'WhatsApp',
style: TextStyle(
color: Colors.white, fontSize: 22.0, fontWeight: FontWeight.w600),
),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Icon(Icons.search),
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(Icons.more_vert),
)
],
backgroundColor: whatsAppGreen,
);
}

This is the result of the code:



Now moving on to

The Tabs

The tabs are a simple extension to the AppBar and Flutter makes it extremely easy to
implement them.

The AppBar has a “bottom” field which holds our tabs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bottom: TabBar(
tabs: <Widget>[
Tab(
icon: Icon(
Icons.camera_alt,
)),
Tab(
child: Text('CHATS'),
),
Tab(
child: Text('STATUS'),
),
Tab(
child: Text('CALLS'),
),
],
indicatorColor: Colors.white,
),

Also,we need a TabController for this to work.

Create a new TabController.

1
2
3
4
5
6
7
TabController tabController;

@override
void initState() {
super.initState();
tabController = TabController(vsync: this, length: 4);
}

Now add that controller to the “controller” field of both the TabBar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bottom: TabBar(
tabs: <Widget>[
Tab(
icon: Icon(
Icons.camera_alt,
)),
Tab(
child: Text('CHATS'),
),
Tab(
child: Text('STATUS'),
),
Tab(
child: Text('CALLS'),
),
],
indicatorColor: Colors.white,
controller: tabController,
),

And for the TabBarView

1
2
3
4
5
6
7
8
9
body: TabBarView(
controller: tabController,
children: <Widget>[
Icon(Icons.camera_alt),
Center(child: Text('Chat Screen')),
Center(child: Text('Status Screen')),
Center(child: Text('Call Screen')),
],
));



Now before going to the individual pages,we’ll add the pages that the tabs
represent.Switch the existing “body” code of the Scaffold with above code.

The children represent the pages that the tabs are an entire page is a
Text widget for now.

Floating Action Button

The Floating Action Button changes depending on which page is on screen.

First add a Floating Action Button in the Scaffold.

1
2
3
4
5
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: fabIcon,
backgroundColor: whatsAppGreenLight,
),

The “fabIcon” field simply stores which icon to display because we need to
change which icon is displayed according to the screen displayed.

To listen to tab selected changes,attach a listener to the TabController.
When tab controller realises the page has changed,change the FAB icon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tabController = TabController(vsync: this, length: 4)
..addListener(() {
setState(() {
switch (tabController.index) {
case 0:
break;
case 1:
fabIcon = Icon(Icons.message);
break;
case 2:
fabIcon = Icon(Icons.camera_enhance);
break;
case 3:
fabIcon = Icon(Icons.call);
break;
}
});
});



Moving on,

The Chat Screen

The Chat Screen has a list of messages we need to display.To make a list of
messages,we use a ListView.builder() and construct our items.

Let’s take a look at the list item for the chat screen.



The outer most widget is a row of one icon and another row.

Inside the second row is a column consisting of one row and one text widget.

The row has the title and message date.

Let’s construct a Chat Item Model as a class for storing the details of the list
item.

1
2
3
4
5
6
class ChatItemModel {
String name;
String mostRecentMessage;
String messageDate;
ChatItemModel(this.name, this.mostRecentMessage, this.messageDate);
}

Right now,I’ve omitted adding a profile picture for brevity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
ListView buildChatListView() {
return ListView.builder(
itemCount: ChatHelper.itemCount,
itemBuilder: (context, position) {
ChatItemModel chatItem = ChatHelper.getChatItem(position);
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Icon(
Icons.account_circle,
size: 64,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
chatItem.name,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20.0,
),
),
Text(chatItem.messageDate,
style: TextStyle(
color: Colors.black45,
))
],
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(chatItem.mostRecentMessage,
style: TextStyle(
color: Colors.black45,
fontSize: 16.0,
)),
)
],
),
),
),
],
),
),
Divider(),
],
);
},
);
}

After creating the first list this is the result:


The other tabviews are very same as the chats’s tab.So we don’t talk about it.
The final code is here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Challenge WhatsApp',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
Color whatsAppGreen = Color.fromRGBO(18, 140, 126, 1.0);
Color whatsAppGreenLight = Color.fromRGBO(37, 211, 102, 1.0);

TabController tabController;

@override
void initState() {
super.initState();
tabController = TabController(vsync: this, length: 4)..addListener(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: buildAppBar(),
body: TabBarView(
controller: tabController,
children: <Widget>[
Icon(Icons.camera_alt),
Scaffold(
body: buildChatListView(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.message),
backgroundColor: whatsAppGreenLight,
),
),
Scaffold(
body: buildStatusListView(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.camera_enhance),
backgroundColor: whatsAppGreenLight,
),
),
Scaffold(
body: buildCallsListView(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.call),
backgroundColor: whatsAppGreenLight,
),
),
],
),
);
}

ListView buildCallsListView() {
return ListView.builder(
itemBuilder: (context, position) {
CallItemModel callItemModel = CallHelper.getCallItem(position);

return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Icon(
Icons.account_circle,
size: 64.0,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
callItemModel.name,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20.0),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
callItemModel.dateTime,
style: TextStyle(
color: Colors.black45, fontSize: 16.0),
),
),
],
),
Icon(
Icons.call,
color: whatsAppGreen,
)
],
),
),
)
],
),
),
Divider(),
],
);
},
itemCount: CallHelper.itemCount,
);
}

ListView buildStatusListView() {
return ListView.builder(
itemBuilder: (context, position) {
StatusItemModel statusItemModel = StatusHelper.getStatusItem(position);

return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Icon(
Icons.account_circle,
size: 64.0,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
statusItemModel.name,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20.0),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
statusItemModel.dateTime,
style: TextStyle(
color: Colors.black45, fontSize: 16.0),
),
)
],
),
),
)
],
),
),
Divider(),
],
);
},
itemCount: StatusHelper.itemCount,
);
}

ListView buildChatListView() {
return ListView.builder(
itemCount: ChatHelper.itemCount,
itemBuilder: (context, position) {
ChatItemModel chatItem = ChatHelper.getChatItem(position);
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Icon(
Icons.account_circle,
size: 64,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
chatItem.name,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20.0,
),
),
Text(chatItem.messageDate,
style: TextStyle(
color: Colors.black45,
))
],
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(chatItem.mostRecentMessage,
style: TextStyle(
color: Colors.black45,
fontSize: 16.0,
)),
)
],
),
),
),
],
),
),
Divider(),
],
);
},
);
}

AppBar buildAppBar() {
return AppBar(
title: Text(
'WhatsApp',
style: TextStyle(
color: Colors.white, fontSize: 22.0, fontWeight: FontWeight.w600),
),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Icon(Icons.search),
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(Icons.more_vert),
)
],
backgroundColor: whatsAppGreen,
bottom: TabBar(
tabs: <Widget>[
Tab(
icon: Icon(
Icons.camera_alt,
)),
Tab(
child: Text('CHATS'),
),
Tab(
child: Text('STATUS'),
),
Tab(
child: Text('CALLS'),
),
],
indicatorColor: Colors.white,
controller: tabController,
),
);
}
}

class ChatItemModel {
String name;
String mostRecentMessage;
String messageDate;
ChatItemModel(this.name, this.mostRecentMessage, this.messageDate);
}

class ChatHelper {
static var chatList = [
ChatItemModel("Alice", "Lunch in the evening?", "16/07/2018"),
ChatItemModel("Jack", "Sure", "16/07/2018"),
ChatItemModel("Jane", "Meet this week?", "16/07/2018"),
ChatItemModel("Ned", "Received!", "16/07/2018"),
ChatItemModel("Steve", "I'll come too!", "16/07/2018")
];

static ChatItemModel getChatItem(int position) {
return chatList[position];
}

static var itemCount = chatList.length;
}

class StatusItemModel {
String name;
String dateTime;

StatusItemModel(this.name, this.dateTime);
}

class StatusHelper {
static var statusList = [
StatusItemModel("Jane", "Yesterday, 11:21 PM"),
StatusItemModel("Jack", "Yesterday, 10:22 PM")
];

static StatusItemModel getStatusItem(int position) {
return statusList[position];
}

static var itemCount = statusList.length;
}

class CallItemModel {
String name;
String dateTime;

CallItemModel(this.name, this.dateTime);
}

class CallHelper {
static var callList = [
CallItemModel("Alice", "Today, 3:39 PM"),
CallItemModel("John", "Today, 4:41 PM")
];

static CallItemModel getCallItem(int position) {
return callList[position];
}

static var itemCount = callList.length;
}