Flutter UI Challenge: News App

Daniel Xav De Oliveira
8 min readSep 25, 2019

These Flutter re-creations will be my attempts to recreate an app UI or design using the Flutter framework.

This challenge involves creating a news application based on a design concept found on Dribble.

Link: https://dribbble.com/shots/6087140-News-App-Design/attachments

Note that the focus will be on the UI rather than actually fetching data. I will use a dummy list of data to best demonstrate.

The app structure

The homepage of the app will require in Flutter:

  1. An AppBar with a vertical menu.
  2. Two text widgets and a divider
  3. Importing custom fonts
  4. Dummy data List, with the title, date, author, category , story and image
  5. A ListView containing the grid, which utilises the GridView.builder, ClipRRect and GridTile widgets.

The detail page of the app will require in Flutter:

  1. An AppBar with a vertical menu.
  2. A ListTile containing the authors signature
  3. Text widgets
  4. A Bottom Navigation bar with 4 items.

Setting up the Project

Let’s make a Flutter project named news_ui and remove all the default code leaving just a blank screen with the default app bar in the main.dart file.

import ‘package:flutter/material.dart’;void main() => runApp(new MyApp());class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: ‘Flutter Demo’,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(“Skype”),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[],
),
),
);
}
}

The AppBar, Text Widgets, Divider & the Custom font

To begin on the design, a vertical menu is added to the AppBar. Furthermore, a main heading was added , using a Text widget. Following this with a solid horizontal line using the Divider widget. Thereunder , is a smaller header using a Text widget.

With regards to the font. A custom font ( RobotoMono) was downloaded from Google fonts : https://fonts.google.com/, and imported into the Flutter project.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme:
new ThemeData(primarySwatch: Colors.blue, fontFamily: 'RobotoMono'),
home: new MyHomePage(),
);
}
}

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

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.white,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.more_vert,
color: Colors.black,
),
onPressed: null)
],
),
body: ListView(
children: <Widget>[
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(40.0, 1.0, 1.0, 1.0),
child: Text(
'News',
style: TextStyle(
color: Colors.black,
fontSize: 35.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
fontFamily: 'RobotoMono'),
),
),
SizedBox(height: 15.0),
SizedBox(
height: 10.0,
child: new Center(
child: new Container(
margin: new EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
height: 5.0,
color: Colors.orange,
),
),
),
SizedBox(height: 30.0),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(40.0, 1.0, 1.0, 1.0),
child: Text(
'Latest',
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
fontWeight: FontWeight.bold),
),
),
SizedBox(height: 30.0),
],
),
);
}
}

The result of the code:

The dummy data list

Next we declare a dummy data list called news_list, with the title, picture, author, date, category and story to be used in both pages.

var news_list = [
{
"name": "In The Bag: iris Law",
"picture": "images/em.jpeg",
"author": "Jack Johnson",
"date": "17/06/2019",
"category": "Latest",
"story": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
},]

Creating the Grid

Next we use the GridView.builder widget.

This widget builds a GridView that displays items, in this case GridTile’s in a two-dimensional scrolling grid, while rendering the data in the list. For the homepage, only the picture and the title will be rendered on each GridTile.

For best practice, it is advised to code this section on a separate Dart file and then import the class as part of a ListView in the original file.

Note

It is the InkWell widget that communicates the data from the list on the homepage to the detail page

import 'package:flutter/material.dart';

import 'article_detail.dart';

class Products extends StatefulWidget {
@override
_ProductsState createState() => _ProductsState();
}

class _ProductsState extends State<Products> {
var news_list = [
{
"name": "In The Bag: iris Law",
"picture": "images/em.jpeg",
"author": "Jack Johnson",
"date": "17/06/2019",
"category": "Latest",
"story":
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
},
{
"name": "Dolore eu fugiot",
"picture": "images/tr.jpg",
"author": "Jack Johnson",
"date": "17/06/2019",
"category": "Latest",
"story":
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
},
{
"name": "In The Bag: iris Law",
"picture": "images/tr.jpg",
"author": "Jack Johnson",
"date": "17/06/2019",
"category": "Latest",
"story":
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
},
{
"name": "Dolore eu fugiot",
"picture": "images/em.jpeg",
"author": "Jack Johnson",
"date": "17/06/2019",
"category": "Latest",
"story":
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
},
];

@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: news_list.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return Single_prod(
art_name: news_list[index]['name'],
art_picture: news_list[index]['picture'],
art_author: news_list[index]['author'],
art_date: news_list[index]['date'],
art_category: news_list[index]['category'],
art_story: news_list[index]['story'],
);
});
}
}

class Single_prod extends StatelessWidget {
final art_name;
final art_picture;
final art_author;
final art_date;
final art_category;
final art_story;

Single_prod({
this.art_name,
this.art_picture,
this.art_author,
this.art_date,
this.art_category,
this.art_story,
});

@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
//here we are parsing the values of the product to the product details page
builder: (context) => ProductDetails(
art_detail_name: art_name,
art_detail_picture: art_picture,
art_detail_author: art_author,
art_detail_date: art_date,
art_detail_category: art_category,
art_detail_story: art_story,
)));
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: GridTile(
child: ClipRRect(
borderRadius: new BorderRadius.circular(17.0),
child: Image.asset(
art_picture,
fit: BoxFit.cover,
),
)),
),
SizedBox(
height: 5.0,
),
Text(
art_name,
style: TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic),
)
],
),
);
}
}

The result of the code:

Setting up the details page

First, the AppBar is created along with its vertical menu action. Following this we create the BottomNavigation bar with its 4 items. I used the ClipRRect widget to give the rounded effect on the BottomNavigation bar.

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class ProductDetails extends StatefulWidget {
final art_detail_name;
final art_detail_picture;
final art_detail_author;
final art_detail_date;
final art_detail_category;
final art_detail_story;

ProductDetails({
this.art_detail_name,
this.art_detail_picture,
this.art_detail_author,
this.art_detail_date,
this.art_detail_category,
this.art_detail_story



});

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

class _ProductDetailsState extends State<ProductDetails> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
backgroundColor: Colors.white,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.more_vert,
color: Colors.black,
),
onPressed: null)
],
),
body: ListView(
children: <Widget>[


],
),
bottomNavigationBar: ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(40),
topLeft: Radius.circular(40),
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
backgroundColor: Colors.orange,
unselectedItemColor: Colors.white,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(FontAwesomeIcons.bolt),
title: Text(''),
),
BottomNavigationBarItem(
icon: Text(
'Latest',
style: TextStyle(color: Colors.white),
),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.short_text),
title: Text(''),
),
],
onTap: null,
),
),
);
}
}

The result of the code:

The Article details

The content of the detail page are children of the ListView widget as was done on the homepage because this widget allows for scrolling, whereas Column widget does not.

To create the author signature the ListTile widget was used as follows:

ListTile(
leading: CircleAvatar(),
title: Text('Author'),
subtitle: Text(widget.art_detail_author),
),

The data from the list was rendered similarly for title, date, category , story and image as can be seen below in the code:

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class ProductDetails extends StatefulWidget {
final art_detail_name;
final art_detail_picture;
final art_detail_author;
final art_detail_date;
final art_detail_category;
final art_detail_story;

ProductDetails(
{this.art_detail_name,
this.art_detail_picture,
this.art_detail_author,
this.art_detail_date,
this.art_detail_category,
this.art_detail_story});

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

class _ProductDetailsState extends State<ProductDetails> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
backgroundColor: Colors.white,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.more_vert,
color: Colors.black,
),
onPressed: null)
],
),
body: ListView(
children: <Widget>[
SizedBox(
height: 10.0,
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 68.0, end: 15.0),
child: ListTile(
leading: CircleAvatar(),
title: Text('Author'),
subtitle: Text(widget.art_detail_author),
),
),
SizedBox(
height: 10.0,
),
SizedBox(
height: 30.0,
width: 300.0,
child: new Center(
child: new Container(
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(
1.0, 1.0, 250.0, 1.0),
child: Text(
widget.art_detail_category,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
margin: new EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
height: 20.0,
color: Colors.orange,
),
),
),
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
child: Text(
widget.art_detail_name,
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
child: Text(
widget.art_detail_date,
style: TextStyle(color: Colors.black45),
),
),
SizedBox(
height: 20.0,
),
Container(
height: 160,
width: 160,
child: GridTile(
child: Padding(
padding:
const EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
child: ClipRRect(
borderRadius: new BorderRadius.circular(17.0),
child: Image.asset(
widget.art_detail_picture,
fit: BoxFit.cover,
),
),
),
),
),
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsetsDirectional.only(start: 38.0, end: 15.0),
child: Text(
widget.art_detail_story,
style: TextStyle(color: Colors.black),
),
),
],
),
bottomNavigationBar: ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(40),
topLeft: Radius.circular(40),
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
backgroundColor: Colors.orange,
unselectedItemColor: Colors.white,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(FontAwesomeIcons.bolt),
title: Text(''),
),
BottomNavigationBarItem(
icon: Text(
'Latest',
style: TextStyle(color: Colors.white),
),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.short_text),
title: Text(''),
),
],
onTap: null,
),
),
);
}
}

The result of the code is as follows:

The complete example is hosted on GitHub:

https://github.com/zorgonred/Flutter_new_ui

Thank you for reading! Feel free to make any other suggestions or recommendations for future challenges. Leave a few claps if you enjoyed it !

--

--

Daniel Xav De Oliveira

My aim is to document my journey as a Software Developer. Writing as I go along. To enforce new knowledge in my mind and to share with others !