A tutorial for creating an Ubuntu Linux Flutter app, using the yaru theme

Overview

Building a Yaru app with Flutter

Summary
URL https://github.com/ubuntu/user_manager
Category
Environment Linux
Status
Feedback Link
Author Frederik Feichtmeier

Introduction

What you'll learn

  • Setting up a Flutter development environment in Ubuntu
  • Building a Flutter app for the Linux desktop, using the yaru theme
  • Using json-server as a local backend to store data

What you'll build


Set up your Flutter environment

Install Flutter on Ubuntu and enable flutter Linux desktop support

sudo snap install flutter
flutter channel beta
flutter upgrade
flutter config --enable-linux-desktop

Install and setup Visual Studio Code

To install VS Code in Ubuntu fire up a terminal and run the following command:

sudo snap install code --classic

Launch VS Code Quick Open (Ctrl+P), paste the following command ...

ext install Dart-Code.flutter

... and press enter to install the official Flutter VS Code extension.

Get started

Open a terminal or the VS Code integrated terminal and run the following command to create a new flutter project for the fictional organization org.flutterfans

flutter create --org org.flutterfans user_manager

Open the project directory user_manager you've just created with VS Code.

You should now see the following directories (1) and the "Linux (linux-x64)" (2) device in use, which is the default on Linux after you've enabled the linux desktop support:

directory_overview

Let's make a small test if the application launches, by opening the lib directory (1) and clicking on the small Run label above void main() (2)

Using the Yaru theme

Open pubspec.yaml - the file for managing of the lifecycle of your app - and add yaru: ^0.0.6 under the dependencies section, so you end up with the following dependencies (check that yaru is on the same column as flutter):

dependencies:
  flutter:
    sdk: flutter
  yaru: ^0.0.6

You can now import the yaru theme by adding

import 'package:yaru/yaru.dart' as yaru;

at the top of main.dart. Use the yaru theme in your app by setting the theme property of MaterialApp to yaru.lightTheme and the darkTheme property to yaru.darkTheme so you end up with the following class definition - your app now follows your system's theme.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: yaru.lightTheme,
      darkTheme: yaru.darkTheme,
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

Building the app

Organize your project structure

You might want to organize your project's directory structure a little bit. Start by creating a view directory under lib by right-clicking the lib directory and click the New folder menu entry and type in view into the textfield that pops out.

Repeat this step with a model and a service directory to end up with the following directory tree above main.dart:

A simple user model

To easily send user data across your app and to the server you want to have a user class, containing only the data properties of your user.

Right-click on the model directory and click on New file and name it user.dart:

Insert the following code

class User {
  int? id;
  String name;

  User({required this.id, required this.name});

  User.empty() : this(id: null, name: "");

  factory User.fromJson(Map<String, dynamic> json) =>
      User(id: json['id'], name: json['name']);

  Map<String, dynamic> toJson() => {"id": id, "name": name};
}

The user service

To let your app communicate via HTTP with your server, add http: ^0.13.1 in your pubspec.yaml under the dependencies section, create user_service.dart in the service directory and paste the following code into:

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:user_manager/model/user.dart';

class UserService {
  UserService({required this.uri});

  final String uri;

  Future<List<User>> getUsers() async {
    final response = await http.get(Uri.http(uri, 'users'));

    if (response.statusCode == 200) {
      var usersJson = jsonDecode(response.body.toString()) as List;
      return usersJson.map((json) => User.fromJson(json)).toList();
    } else {
      throw Exception(response.statusCode.toString() + ': ' + response.body);
    }
  }

  Future saveUser(User user) async {
    for (var aUser in await getUsers()) {
      if (aUser.id == user.id) {
        await updateUser(user);
        return;
      }
    }
    await addUser(user);
  }

  Future<http.Response> addUser(User user) async {
    return await http.post(Uri.http(uri, 'users'),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode(user.toJson()));
  }

  Future updateUser(User user) async {
    return await http.put(Uri.http(uri, 'users/${user.id}'),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode(user.toJson()));
  }

  Future removeUser(User user) async {
    await http.delete((Uri.http(uri, 'users/${user.id}')));
  }
}

The UI

One card for each user

Each user that will be managed is represented by a card inside the UI.

Create user_card.dart in the view directory and paste in the following code

import 'package:flutter/material.dart';
import 'package:user_manager/model/user.dart';

class UserCard extends StatefulWidget {
  final User user;
  final VoidCallback onDelete;
  final VoidCallback onEdit;

  UserCard({required this.user, required this.onDelete, required this.onEdit});

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

class _UserCardState extends State<UserCard> {
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.person),
            title: Text(widget.user.name),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              TextButton(
                child: const Text('Edit'),
                onPressed: () => widget.onEdit(),
              ),
              const SizedBox(width: 8),
              TextButton(
                child: const Text('Remove'),
                onPressed: () => widget.onDelete(),
              ),
              const SizedBox(width: 8),
            ],
          ),
        ],
      ),
    );
  }
}

A dialog to edit users in

A user will be edited inside a dialog, spawned from clicking on the either the big green floating action button or the edit button of your user card.

Create the file user_edit_dialog.dart inside the view directory and copy/paste the following code into:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class UserEditDialog extends StatefulWidget {
  final TextEditingController nameController;
  final AsyncCallback editUser;

  UserEditDialog({required this.nameController, required this.editUser});

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

class _UserEditDialogState extends State<UserEditDialog> {
  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: Container(
        height: 150,
        width: 200,
        child: Padding(
          padding: const EdgeInsets.all(10.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: TextField(
                  autofocus: true,
                  controller: widget.nameController,
                  decoration: InputDecoration(hintText: 'User name'),
                ),
              ),
              Row(
                children: [
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: ElevatedButton(
                      onPressed: () async =>
                          Navigator.of(context).pop(widget.editUser()),
                      child: Text(
                        "Save",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextButton(
                      onPressed: () => Navigator.of(context).pop(),
                      child: Text("Cancel"),
                    ),
                  )
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

The new home page

The old home page won't be enough anymore so you need a new one to load the user cards into. The home page uses the user service to manage the user data.

Open your pubspec.yaml file again and add injector: ^2.0.0 in the dependencies section. Create the file home_page.dart in the view directory and paste the following code:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:injector/injector.dart';
import 'package:user_manager/model/user.dart';
import 'package:user_manager/service/user_service.dart';
import 'package:user_manager/view/user_card.dart';
import 'package:user_manager/view/user_edit_dialog.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late TextEditingController _nameController;
  final _userService = Injector.appInstance.get<UserService>();

  @override
  void initState() {
    _nameController = TextEditingController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Scrollbar(
            child: FutureBuilder(
                future: _userService.getUsers(),
                builder: (context, AsyncSnapshot<List<User>> snapShot) {
                  if (snapShot.hasData) {
                    return ListView(
                      children: snapShot.data!
                          .map((user) => UserCard(
                              user: user,
                              onDelete: () {
                                _userService
                                    .removeUser(user)
                                    .then((value) => setState(() {
                                          _showSnackBar(
                                              'Deleted user: ' + user.name);
                                        }))
                                    .onError((error, stackTrace) =>
                                        _showSnackBar(error.toString()));
                              },
                              onEdit: () => editUser(user, context)))
                          .toList(),
                    );
                  }
                  return CircularProgressIndicator();
                }),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          _nameController.text = "";
          editUser(new User.empty(), context);
        },
      ),
    );
  }

  void editUser(User user, BuildContext context) {
    _nameController.text = user.name;
    showDialog(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return UserEditDialog(
              nameController: _nameController,
              editUser: () async {
                user.name = _nameController.text;
                _userService
                    .saveUser(user)
                    .then((value) => setState(() {
                          _showSnackBar('Saved user: ' + user.name);
                        }))
                    .onError(
                        (error, stackTrace) => _showSnackBar(error.toString()));
              });
        });
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(duration: const Duration(seconds: 1), content: Text(message)));
  }
}

Finish up your main.dart

The UI of the test app is no longer needed. Remove all Widgets except MyApp, change the home property to HomePage(), change your main() function to by asynchronous, returning a Future<void> and register your UserService with the Injector class before you run your app. To be sure you've imported everything correctly, replace all code inside main.dart with the following new version:

import 'package:flutter/material.dart';
import 'package:injector/injector.dart';
import 'package:user_manager/service/user_service.dart';
import 'package:user_manager/view/home_page.dart';
import 'package:yaru/yaru.dart' as yaru;

Future<void> main() async {
  final userService = UserService(uri: 'localhost:3000');
  Injector.appInstance.registerDependency<UserService>(() => userService);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: yaru.lightTheme,
      darkTheme: yaru.darkTheme,
      home: HomePage(),
    );
  }
}

Create a local server to process client requests

Create mock data

Create db.json inside the root folder of your application and insert some fictional users in the json format:

{
    "users": [
        {
            "id": 1,
            "name": "Carlo"
        },
        {
            "id": 2,
            "name": "Mads"
        },
        {
            "id": 3,
            "name": "Frederik"
        },
        {
            "id": 4,
            "name": "Stuart"
        },
        {
            "id": 5,
            "name": "Paul"
        },
        {
            "id": 6,
            "name": "Muq"
        }
    ]
}

Install node-js and json-server

We will use a fantastic local mock server to let our client talk to. To do so we need the node snap. Enter the following commands to install node and json-server.

sudo snap install node --classic
sudo npm install -g json-server

Entering the user management :)

Start your json-server by running the following command

json-server --watch db.json

Start your app again! Done :) You can now manage users inside a Ubuntu Linux app, using the yaru theme:

You might also like...

A clean front-end to Volumio, the Linux distribution for music playback. DigiPlayer is written in Flutter.

A clean front-end to Volumio, the Linux distribution for music playback. DigiPlayer is written in Flutter.

EN | 中文 DigiPlayer - A Clean Touch UI for Volumio 3 DigiPlayer is a clean touch UI for Volumio 3, written in Flutter for the Raspberry Pi Touch Displa

Jul 26, 2022

Front-end of multiplayer web3 games implemented with Flutter to run on all platforms (Web, Android, iOS, Linux, Window, macOS, TV-OS)

Front-end of multiplayer web3 games implemented with Flutter to run on all platforms (Web, Android, iOS, Linux, Window, macOS, TV-OS)

Nov 15, 2022

The Flutter Planets app tutorial with commits per lesson

flutter planets tutorial This project contains the steps followed on the tutorial on sergiandreplace.com. Each lesson will correspond to a single bran

Jan 7, 2023

Flutter Theme Manager with Abstract Factory Design

extheme A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

Dec 23, 2022

A Application for Address Update Theme UIDAI Hackathon 2021

A  Application for Address Update Theme UIDAI Hackathon 2021

Address_update_Theme_UIDAI_Hackathon Address Update Theme UIDAI Hackathon Application for Address Update Theme UIDAI Hackathon 2021. General This is a

Nov 2, 2021

Dynamically changing your theme without hassle

Dynamically changing your theme without hassle

dynamic_theme Dynamically changing your theme without hassle This packages manages changing your theme during runtime and persiting that theme. I wrot

Dec 29, 2022

A powerful Flutter chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.

A powerful Flutter chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.

style .button-9 { appearance: button; backface-visibility: hidden; background-color: #1d52d9; border-radius: 6px; border-width: 0; box-shadow: rgba(

Aug 11, 2023

Berikut ini merupakan code tutorial flutter UI yang didesain oleh Muhammad Najib

mytsel A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this i

Dec 25, 2022

GitHub Desktop tutorial repository

demo_bloc_infinitylist A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you s

Nov 4, 2021
Owner
Ubuntu
Ubuntu
🎞 Flutter media playback, broadcast & recording library for Windows, Linux & macOS. Written in C++ using libVLC & libVLC++. (Both audio & video)

dart_vlc Flutter media playback, broadcast, recording & chromecast library for Windows, Linux & macOS. Written in C++ using libVLC & libVLC++. Install

Hitesh Kumar Saini 417 Dec 29, 2022
🎵 Elegant music app to play local music & YouTube music. Distributes music into albums & artists. Has playlists & lyrics. Windows + Linux + Android.

Harmonoid Elegant music app to play local music & YouTube music. Download Now ?? Windows, Linux & Android. Feel free to report bugs & issues. Loving t

Harmonoid 1.9k Aug 10, 2022
Pikitia is a social mobile app developed with Flutter, focused around the theme of a Pikit.

Pikitia Pikitia is a social mobile app developed with Flutter, focused around the theme of a Pikit. A Pikit is simply a photo, associated with the loc

Léo Salé 3 Dec 25, 2022
Messaging App Light and Dark Theme

Messaging App Light and Dark Theme

null 1 Sep 26, 2021
Flutter tools for embedded Linux (eLinux)

Flutter for Embedded Linux (eLinux) This software is a non-official extension to the Flutter SDK to build Flutter apps for Embedded Linux devices. Obj

Sony 212 Jan 4, 2023
A curated list of awesomeness for Flutter Linux.

Awesome Flutter Linux ?? A curated list of awesomeness for Flutter Linux. Packages Adwaita Icons - Package that contains all icons built for the Adwai

J-P Nurmi 111 Dec 26, 2022
A cross-platform (Android/Windows/macOS/Linux) USB plugin for Flutter

quick_usb A cross-platform (Android/Windows/macOS/Linux) USB plugin for Flutter Usage List devices List devices with additional description Get device

Woodemi Co., Ltd 39 Oct 1, 2022
A Flutter plugin to read 🔖 metadata of 🎵 media files. Supports Windows, Linux & Android.

flutter_media_metadata A Flutter plugin to read metadata of media files. A part of Harmonoid open source project ?? Install Add in your pubspec.yaml.

Harmonoid 60 Dec 2, 2022
Flutter window blur & transparency effects for on Windows & Linux. 💙

flutter_acrylic Window blur & transparency effects for Flutter on Windows & Linux Installation Mention in your pubspec.yaml. dependencies: ... flu

Hitesh Kumar Saini 438 Jan 2, 2023
A Flutter plugin for handling Connectivity and REAL Connection state in the mobile, web and desktop platforms. Supports iOS, Android, Web, Windows, Linux and macOS.

cross_connectivity A Flutter plugin for handling Connectivity and REAL Connection state in the mobile, web and desktop platforms. Supports iOS, Androi

MarchDev Toolkit 29 Nov 15, 2022