...

/

A Classic Flutter App

A Classic Flutter App

Review the UI of a Flutter application that’s tailored for smartphones.

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
The pubspec.yaml used in the rest of this chapter

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 code of the application used in the rest of this chapter

The main.dart file contains the start of the application. It consists of a DevicePreview ...