We're gonna make a dice roll app called GDiceSC in zapp.run/edit/flutter.

Replace the starter app with a base to start from.

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp( const MaterialApp(
    title: "GDiceSC",
    home: GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Hello World!'),
      ),
    );
  }
}

Build a layout common among most mobile apps.

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp( const MaterialApp(
    title: "GDiceSC",
    home: GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "GDiceSC" ),
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: (selectedIndex) => setState( () => _currentIndex = selectedIndex ),
        children: const [
          Placeholder(),
          Placeholder(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (selectedIndex) {
          setState( () => _currentIndex = selectedIndex );
          _pageController.jumpToPage(_currentIndex);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: "Home",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: "Settings",
          ),
        ],
      ),
    );
  }
}

Create home.dart and settings.dart to build the home and settings page then import them in main.dart.

home.dart

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Home'),
        ),
      ),
    );
  }
}

settings.dart

import 'package:flutter/material.dart';

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPage();
}

class _SettingsPage extends State<SettingsPage> {

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Settings'),
        ),
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';

import 'home.dart';     // import home.dart
import 'settings.dart'; // import settings.dart

void main() {
  runApp( const MaterialApp(
    title: "GDiceSC",
    home: GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "GDiceSC" ),
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: (selectedIndex) => setState( () => _currentIndex = selectedIndex ),
        children: const [
          HomePage(),       // replace placeholder with home page
          SettingsPage(),   // replace placeholder with settings page
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (selectedIndex) {
          setState( () => _currentIndex = selectedIndex );
          _pageController.jumpToPage(_currentIndex);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: "Home",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: "Settings",
          ),
        ],
      ),
    );
  }
}

Let the user set how many sides the dice will have on the settings page.

settings.dart

import 'package:flutter/material.dart';

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

double sides = 6;

class _SettingsPageState extends State<SettingsPage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Dice sides: ${sides.round()}",
              style: const TextStyle( fontSize: 32 )
            ),
            Slider(
              min: 2,
              max: 20,
              divisions: 18,
              value: sides,
              onChanged: (value) => setState( () => sides = value ),
            ),
          ],
        ),
      ),
    );
  }
}

Let the user roll the dice in the home page.

home.dart

import 'dart:math';

import 'package:flutter/material.dart';

import 'settings.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  int _prev = 0;
  int _curr = 0;

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text( "Previous roll: $_prev" ),
            Text(
              "$_curr",
              style: const TextStyle( fontSize: 256 ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon( Icons.refresh ),
        onPressed: () {
          setState( () {
            _prev = _curr;
            _curr = Random().nextInt( sides.round() ) + 1;
          });
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }
}

Change colors and other visual features with theme data.

main.dart

import 'package:flutter/material.dart';

import 'home.dart';
import 'settings.dart';

void main() {
  runApp( MaterialApp(
    theme: ThemeData( // customize your app theme with theme data
      appBarTheme: const AppBarTheme(
        centerTitle: true,
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      floatingActionButtonTheme: const FloatingActionButtonThemeData(
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      sliderTheme: const SliderThemeData(
        activeTrackColor: Colors.red,
        inactiveTrackColor: Colors.black,
        thumbColor: Colors.red,
      ),
      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
        backgroundColor: Colors.red,
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.black,
      ),
    ),
    title: "GDiceSC",
    home: const GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "GDiceSC" ),
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: (selectedIndex) => setState( () => _currentIndex = selectedIndex ),
        children: const [
          HomePage(),
          SettingsPage(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (selectedIndex) {
          setState( () => _currentIndex = selectedIndex );
          _pageController.jumpToPage(_currentIndex);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: "Home",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: "Settings",
          ),
        ],
      ),
    );
  }
}

Prepare to add user authentication with Firebase in a future workshop.

account.dart

import 'package:flutter/material.dart';

class AccountPage extends StatefulWidget {
  const AccountPage({super.key});

  @override
  State<AccountPage> createState() => _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "My Account" ),
        automaticallyImplyLeading: false,
      ),
      body: const Center( child: Text("Account"), ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: (selectedIndex) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(
                selectedIndex == 0 ?
                "Account changes saved!" :
                "Account changes discarded"
              ),
              action: SnackBarAction(
                label: "Ok",
                onPressed: () {},
              )
            )
          );
          Navigator.pop(context);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.save),
            label: "Save",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.delete),
            label: "Discard",
          ),
        ],
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';

import 'account.dart';
import 'home.dart';
import 'settings.dart';

void main() {
  runApp( MaterialApp(
    theme: ThemeData(
      appBarTheme: const AppBarTheme(
        centerTitle: true,
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      floatingActionButtonTheme: const FloatingActionButtonThemeData(
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      sliderTheme: const SliderThemeData(
        activeTrackColor: Colors.red,
        inactiveTrackColor: Colors.black,
        thumbColor: Colors.red,
      ),
      snackBarTheme: const SnackBarThemeData(
        backgroundColor: Colors.white,
        actionTextColor: Colors.red,
        contentTextStyle: TextStyle(
          color: Colors.black,
        ),
        behavior: SnackBarBehavior.floating,
      ),
      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
        backgroundColor: Colors.red,
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.black,
      ),
    ),
    title: "GDiceSC",
    home: const GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "GDiceSC" ),
        actions: [
          IconButton(
            icon: const Icon( Icons.person ),
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute( builder: (context) => const AccountPage() ),
              );
            },
          ),
        ],
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: (selectedIndex) => setState( () => _currentIndex = selectedIndex ),
        children: const [
          HomePage(),
          SettingsPage(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (selectedIndex) {
          setState( () => _currentIndex = selectedIndex );
          _pageController.jumpToPage(_currentIndex);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: "Home",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: "Settings",
          ),
        ],
      ),
    );
  }
}

Mark your app as complete by removing the debug banner.

main.dart

import 'package:flutter/material.dart';

import 'account.dart';
import 'home.dart';
import 'settings.dart';

void main() {
  runApp( MaterialApp(
    debugShowCheckedModeBanner: false, // removes the red debug banner on the top right corner
    theme: ThemeData(
      appBarTheme: const AppBarTheme(
        centerTitle: true,
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      floatingActionButtonTheme: const FloatingActionButtonThemeData(
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      sliderTheme: const SliderThemeData(
        activeTrackColor: Colors.red,
        inactiveTrackColor: Colors.black,
        thumbColor: Colors.red,
      ),
      snackBarTheme: const SnackBarThemeData(
        backgroundColor: Colors.white,
        actionTextColor: Colors.red,
        contentTextStyle: TextStyle(
          color: Colors.black,
        ),
        behavior: SnackBarBehavior.floating,
      ),
      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
        backgroundColor: Colors.red,
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.black,
      ),
    ),
    title: "GDiceSC",
    home: const GDiceSC(),
  ));
}

class GDiceSC extends StatefulWidget {
  const GDiceSC({super.key});

  @override
  State<GDiceSC> createState() => _GDiceSCState();
}

class _GDiceSCState extends State<GDiceSC> {

  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text( "GDiceSC" ),
        actions: [
          IconButton(
            icon: const Icon( Icons.person ),
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute( builder: (context) => const AccountPage() ),
              );
            },
          ),
        ],
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: (selectedIndex) => setState( () => _currentIndex = selectedIndex ),
        children: const [
          HomePage(),
          SettingsPage(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (selectedIndex) {
          setState( () => _currentIndex = selectedIndex );
          _pageController.jumpToPage(_currentIndex);
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: "Home",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: "Settings",
          ),
        ],
      ),
    );
  }
}

Good job.