Flutter/Dart: Developing and deploying a simple App


Overview

Here in this post, we document a simple app to measure behaviours over time. The app will have two pages (routes). One for entering data (counting behaviour/activity events), the other one for showing the data as curves, the number of behavioural events over time periods. The entered data will be at first hard coded, later we save it in a more proper way in a file or in a DB. This is not a tutorial, but rather a documentation of the app, which will be changed and updated as the simple application is implemented. So, you might take ideas from that which might be useful for your case.

At the end of the documentation, there will be a section to explain how to deploy the app on your mobile phone.

The app is developed using the Android Studio

Disclosure: I have included affiliate links on this page which means that I may receive a commission (at zero cost to you) if a purchase is made through any of those links. See my Disclaimer for more details.

The first page (route)

The first page of the application looks currently like this:

It’s basically a simple list of behaviours for which the user can count the events he/she was engaged in that particular behaviour.

The goal of such behaviour monitoring apps is to see how positive behaviours, like doing exercise, for example, increase over time and how negative behaviours, like for example smoking decrease over time.

This is the first and the main page of our simple application which is implemented by the class _MyHomePageState (see the code section)

The second page (route)

If you click on the arrow button at the bottom right of the first page, you get to the second page.

The second page should show the curves of the behaviour events over time. But currently, the second page is not very far with its implementation and shows just a hard-coded dummy curve:

The second page is implemented by the class _BehaviourTrendsPageState which does not yet contain any curves or graphical features yet and is in a very early stage.

First tests with graphics are documented here (Flutter/Dart Drawing examples), which will be applied to this application over time.

Code

The code for this simple application is as follows (will change over time, because the implementation of the app is ongoing):

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var routes = <String, WidgetBuilder>{
      BehaviourTrendsPage.routeName: (BuildContext context) => BehaviourTrendsPage(title: "BehaviourTrendsPage"),
    };
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: routes,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counterProgramming = 0;
  int _counterExercise = 0;
  int _counterEatingFruits = 0;

  void _incrementCounterProgramming() {
    setState(() {
      _counterProgramming++;
    });
  }

  void _decrementCounterProgramming() {
    setState(() {
      _counterProgramming--;
    });
  }

  void _incrementCounterExercise() {
    setState(() {
      _counterExercise++;
    });
  }

  void _decrementCounterExercise() {
    setState(() {
      _counterExercise--;
    });
  }

  void _incrementCounterEatingFruits() {
    setState(() {
      _counterEatingFruits++;
    });
  }

  void _decrementCounterEatingFruits() {
    setState(() {
      _counterEatingFruits--;
    });
  }

  void _goToCurvesPage() {
    Navigator.pushNamed(context, BehaviourTrendsPage.routeName);
  }

  @override
  Widget build(BuildContext context) {
    var button =  IconButton(icon:  Icon(Icons.access_alarm), onPressed: _onButtonPressed);
    return  Scaffold(
      appBar:  AppBar(
        title:  Text(widget.title),
      ),
      body: Center(
          child: ListView(
            padding: const EdgeInsets.all(8),
            children: <Widget>[
              Container(
                  height: 50,
                  color: Colors.amber[100],
                  child:Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: <Widget>[
                        const Text(
                          'Learning about programming',
                          textAlign: TextAlign.left,
                        ),
                        Text(
                          '$_counterProgramming',
                          style: Theme.of(context).textTheme.headline4,
                        ),
                        Spacer(),
                        FloatingActionButton(
                          onPressed: _decrementCounterProgramming,
                          tooltip: 'Increment',
                          heroTag: 'Increment',
                          child: const Icon(Icons.remove),
                        ),
                        FloatingActionButton(
                          onPressed: _incrementCounterProgramming,
                          tooltip: 'Decrement',
                          heroTag: 'Decrement',
                          child: const Icon(Icons.add),
                        ),
                      ]
                  )),
              Container(
                  height: 50,
                  color: Colors.amber[500],
                  child:Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: <Widget>[
                        const Text(
                          'Doing exercises',
                          textAlign: TextAlign.left,
                        ),
                        Text(
                          '$_counterExercise',
                          style: Theme.of(context).textTheme.headline4,
                        ),
                        Spacer(),
                        FloatingActionButton(
                          onPressed: _decrementCounterExercise,
                          tooltip: 'Increment',
                          heroTag: 'Increment',
                          child: const Icon(Icons.remove),
                        ),
                        FloatingActionButton(
                          onPressed: _incrementCounterExercise,
                          tooltip: 'Decrement',
                          heroTag: 'Decrement',
                          child: const Icon(Icons.add),
                        ),
                      ]
                  )),
              Container(
                  height: 50,
                  color: Colors.amber[100],
                  child:Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: <Widget>[
                        const Text(
                          'Eating fruits',
                          textAlign: TextAlign.left,
                        ),
                        Text(
                          '$_counterEatingFruits',
                          style: Theme.of(context).textTheme.headline4,
                        ),
                        Spacer(),
                        FloatingActionButton(
                          onPressed: _decrementCounterEatingFruits,
                          tooltip: 'Increment',
                          heroTag: 'Increment',
                          child: const Icon(Icons.remove),
                        ),
                        FloatingActionButton(
                          onPressed: _incrementCounterEatingFruits,
                          tooltip: 'Decrement',
                          heroTag: 'Decrement',
                          child: const Icon(Icons.add),
                        ),
                      ]
                  )),
            ],
          )
      ),
      floatingActionButton:  FloatingActionButton(
        onPressed: _goToCurvesPage,
        tooltip: 'Forward',
        heroTag: 'Forward',
        child:  const Icon(Icons.navigate_next),
      ),
    );
  }

  void _onButtonPressed() {
    Navigator.pushNamed(context, BehaviourTrendsPage.routeName);
  }
}

class BehaviourTrendsPage extends StatefulWidget {
  BehaviourTrendsPage({Key? key, required this.title}) : super(key: key);

  static const String routeName = "/BehaviourTrendsPage";

  final String title;

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

class _BehaviourTrendsPageState extends State<BehaviourTrendsPage> {

  List<Offset> points = const [
    Offset(10, 400),
    Offset(50, 450),
    Offset(100, 420),
    Offset(150, 350),
    Offset(200, 380),
    Offset(250, 460),
    Offset(300, 420),
    Offset(350, 330)
  ];

  @override
  Widget build(BuildContext context) {
    var button =  IconButton(icon:  const Icon(Icons.arrow_back), onPressed: _onButtonPressed);
    return Scaffold(
      appBar: AppBar(
        title:  Text(widget.title),
      ),
      body: Center(
          child: ListView(
              padding: const EdgeInsets.all(10),
              children: <Widget>[
                Container(
                    height: 720,
                    width: 20,
                    color: Colors.yellow,
                    child:CustomPaint(
                      child: Container(child: Text('This is a test drawing'),),
                      painter: LineGraphPainter(this.points),
                    )
                )
              ]
          )
      ),
      floatingActionButton:  FloatingActionButton(
        onPressed: _onButtonPressed,
        tooltip: 'Backward',
        child:  const Icon(Icons.navigate_before),
      ),
    );
  }

  void _onFloatingActionButtonPressed() {
  }

  void _onButtonPressed() {
    Navigator.pop(context);
  }

  @override
  void paint(Canvas canvas, Size size) {
    final p1 = Offset(50, 50);
    final p2 = Offset(250, 150);
    final paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 4;
    canvas.drawLine(p1, p2, paint);
  }
}

class LineGraphPainter extends CustomPainter {
  final List<Offset> points;
  LineGraphPainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    var center = size / 2;
    var paint = Paint()..color = Colors.red;
    for (int i = 0; i < points.length - 1; i++) {
      Offset start = Offset(this.points[i].dx, this.points[i].dy);
      Offset end = Offset(this.points[i + 1].dx, this.points[i + 1].dy);
      canvas.drawLine(start, end, paint);
    }
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

To be continued

Related topics