A Classic Flutter App
Review the UI of a Flutter application that’s tailored for smartphones.
We'll cover the following...
In this chapter, we’ll look into the UI of a typical Flutter application designed to be used on a smartphone. This application allows us to see a list of courses we’re following with a visual indication of their progress. We can also see a more detailed view for each course and support screens, like a profile page and a settings page.
We’ll only implement the application’s UI. All the data we’ll need is hardcoded fake data. The goal of this chapter is to identify when the UI is not responsive. Therefore, we don’t need a real data-connected application.
Getting started
First, let’s have a look at the pubspec.yaml
file. We have added the device_preview
package to make it easier to switch between different devices and the percent_indicator
package to show the progress of each course.
name: courses_app description: Your courses. publish_to: 'none' version: 1.0.0+1 environment: sdk: ">=2.15.1 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 # Used in the UI to show how much of the course you have followed percent_indicator: 3.4.0 # Preview the app in different devices device_preview: 1.0.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 flutter: uses-material-design: true
In the pubspec.yaml
in lines 18 and 21, we add the current versions of the two packages. Now, let’s see the rest of the project:
import 'package:flutter/material.dart'; import 'data/course.dart'; import 'completed_indicator.dart'; class CourseDetails extends StatelessWidget { final Course course; const CourseDetails({Key? key, required this.course}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( bottom: false, child: Stack( children: <Widget>[ Column( children: <Widget>[ // The scrollable widget takes all available space, after the // bottom area takes whatever it needs. Expanded(child: _scrollableView()), _bottomArea(), ], ), Positioned( child: FloatingActionButton( onPressed: () { // Navigate back. Navigator.of(context).pop(); }, child: const Icon(Icons.close), mini: true, ), top: 16, left: 16, ) ], ), ), ); } Widget _scrollableView() { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Column( children: <Widget>[ Container( height: 200, color: course.color, child: const Center( child: Icon(Icons.image), ), ), const SizedBox(height: 61), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ Container( color: Colors.grey, height: 0.5, ), const SizedBox(height: 20), Align( alignment: Alignment.centerLeft, child: Text( course.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w800, ), ), ), const SizedBox(height: 5), Align( alignment: Alignment.centerLeft, child: Text( course.description, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w300, color: Colors.grey, ), ), ), const SizedBox(height: 30), ...course.lessons.map((lesson) { return Row( children: [ const Text("• "), Expanded( child: Text(lesson), ), ], ); }), ], ), ), const SizedBox(height: 20), ], ), ), ); } Widget _bottomArea() { return Container( decoration: BoxDecoration(color: Colors.white, boxShadow: [ // Box shadow. It must be visible only on the top. BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: -1, blurRadius: 7, offset: const Offset(0, -6), ) ]), child: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: CompletedIndicator( percent: course.completed, color: course.color), ), ), ); } }
The main.dart
file contains the start of the application. It consists of a DevicePreview
...