Written by Krzysztof Len
JS Fullstack Gdańsk
Published April 15, 2021

Code with us #02 – Kudos list

Welcome to the second episode of the “Code with us” Flutter series. In this episode, we will build together a list of kudos in which every single item is presented as a separated tile with the names of the giver, recipient, and a description. At the very top of the list, we will create a horizontal slider with a list of available rewards which can be exchanged for your kudos. Let’s get it started!

1. Preparation

The same as previously we have to create a new project using the flutter command line, which requires Flutter SDK installed on your machine. If you don’t remember how to do that you can follow the reference instruction under this link.

You can watch the video instead of reading the article, or use them both:

You can name your project however you want. But in this episode we use kudos_wall a name that will be created by the following:

flutter create kudos_wall

Next, create an assets folder and inside it create an images folder, and copy all attachments images from the zip file.
Inside of /lib folder create a new stateless widget kudos_wall.dart and, import it in the main.dart file and place it in the home property of the MaterialApp. In that moment your main file should look quite like this:

import 'package:flutter/material.dart';

import 'kudos_wall.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Kudify app',
      home: KudosWall(),
    );
  }
}

And the kudos_wall.dart:

import 'package:flutter/material.dart';

class KudosWall extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("Kudos wall"),
    );
  }
}

2. Models

The first step we have to take is to create a model folder in which we create a Reward class in the reward.dart file with the following structure:

class Reward {
  String name;
  String imageUrl;

  Reward(this.name, this.imageUrl);
}

This model will get a name and an imageUrl as a parameter.
The next models are the Kudos one, Recipient, and Sender which we will create inside kudos.dart file after creating it.
Your file should look like this:

class Kudos {
  Recipient recipient;
  Sender sender;
  String description;

  Kudos(
    String recipientName,
    String senderName,
    String senderAvatarUrl,
    String description,
  ) {
    this.recipient = Recipient(recipientName);
    this.sender = Sender(senderName, senderAvatarUrl);
    this.description = description;
  }
}

class Recipient {
  String name;

  Recipient(this.name);
}

class Sender {
  String name;
  String avatarUrl;

  Sender(this.name, this.avatarUrl);
}

 

3. Mocks

Because we are not using any API to display data in our list we will have to mock our kudos. To do that create a mocks.dart file in the root of the lib folder. Inside of it, we’ll create a Mocks class which will hold a list of the rewarded and recent kudos using previously created models. The rewards variable should look like this:

import 'models/kudos.dart';
import 'models/reward.dart';

class Mocks {
  static final List rewards = [
    Reward("Dragon's egg", "assets/images/dragon.png"),
    Reward("Butterbeer", "assets/images/beer-bottle.png"),
    Reward("Toss a coin", "assets/images/coin.png"),
    Reward("Cinema tickets", "assets/images/tickets.png"),
    Reward("Large coffee", "assets/images/coffee.png")
  ];

  static final List recentKudoses = [
    Kudos(
      "Mike",
      "Michalina",
      "assets/images/avatar1.png",
      "Many thanks for helping me with the broken internet connection",
    ),
    Kudos(
      "Karolina",
      "Adi",
      "assets/images/avatar2.png",
      "Kudos for a well prepared designs. I really love them",
    ),
    Kudos(
      "Karolina",
      "Karolina",
      "assets/images/avatar3.png",
      "Kudos for a great presentation during the last meeting with the team",
    ),
    Kudos(
      "Robert",
      "Lukas",
      "assets/images/avatar4.png",
      "Well prepared videos! Keep it going :)",
    ),
    Kudos(
      "Adi",
      "Thomas",
      "assets/images/avatar2.png",
      "For the delicious coffee and doughnuts we've had recently together",
    ),
    Kudos(
      "Przemek",
      "Jake",
      "assets/images/avatar6.png",
      "Thanks for an awesome presentation during our monthly talks",
    ),
    Kudos(
      "Rafał",
      "Elizabeth",
      "assets/images/avatar3.png",
      "For keeping an eye on our web page, many thanks!",
    ),
    Kudos(
      "Thomas",
      "Karolina",
      "assets/images/avatar3.png",
      "Thanks for letting me know about the recent changes in our work environment",
    ),
    Kudos(
      "Rafał",
      "Robert",
      "assets/images/avatar2.png",
      "Great job with the latest update of our web page",
    ),
  ];
}

Feel free to copy both data to your project.

4. Theme

The next step we will take is to create reusable color variables. To do that we need to create a theme_colors.dart file in the root of the lib folder.

This leads us to use the same color variable in the whole application and to the possibility to change them only in one place. This is how it should look like the ThemeColors class:

class ThemeColors {
  static const Color lightGrey = Color.fromRGBO(223, 229, 235, 1);
  static const Color darkGrey = Color.fromRGBO(64, 64, 64, 1);
}

Also, the main.dart we will add a theme property to MaterialApp widget in which we will have a possibility to declare basic typography which will be used by default in the app.

Inside the theme property add the card theme like the following:

theme: ThemeData(
        cardTheme: CardTheme(
          shape: RoundedRectangleBorder(
            side: BorderSide(
              color: ThemeColors.lightGrey,
            ),
            borderRadius: BorderRadius.circular(6),
          ),
        ),
        textTheme: TextTheme(
          headline1: TextStyle(
            fontWeight: FontWeight.w900,
            fontSize: 20.0,
          ),
          headline2: TextStyle(
            fontWeight: FontWeight.w800,
            fontSize: 14.0,
          ),
          headline3: TextStyle(
            fontWeight: FontWeight.w600,
            fontSize: 14.0,
          ),
          subtitle1: TextStyle(
            fontWeight: FontWeight.w600,
            fontSize: 16.0,
          ),
        ).apply(
          displayColor: ThemeColors.darkGrey,
        ),
      ),

 

5. Kudos wall

With all that preparation now we are ready to implement the kudos wall list. Inside kudos_wall.dart file replaces a child of the container with the CustomScrollView widget class.
Next, we will start by creating a private method to display subtitles in a list.

child: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: _buildListSubtitle(context, "PICK A REWARD"),
          ),
        ],
      ),

And the method itself:

  Widget _buildListSubtitle(BuildContext context, String subtitle) {
    return Padding(
      padding: const EdgeInsets.only(
        top: 30.0,
        left: 16.0,
        bottom: 10.0,
      ),
      child: Text(
        subtitle,
        style: Theme.of(context).textTheme.headline2,
      ),
    );
  }

Next, we add another SliverToBoxAdapter class to show a list of available rewards. We use a
ListView.builder for better performance and create another private method _buildRewardListTile to create a single reward:

SliverToBoxAdapter(
            child: Container(
              height: 120.0,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                itemCount: Mocks.rewards.length,
                itemBuilder: (BuildContext context, int index) =>
                    _buildRewardListTile(context, index),
              ),
            ),
          ),

And the method:

Widget _buildRewardListTile(BuildContext context, int index) {
    bool hasLeftmargin = index == 0;
    Reward reward = Mocks.rewards[index];

    return Card(
      elevation: 0,
      margin: hasLeftmargin
          ? const EdgeInsets.symmetric(horizontal: 16.0)
          : const EdgeInsets.only(right: 16.0),
      child: Container(
        width: 150.0,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              SizedBox(
                height: 10.0,
              ),
              Image.asset(
                reward.imageUrl,
                height: 50.0,
              ),
              SizedBox(
                height: 10.0,
              ),
              Text(
                reward.name,
                style: Theme.of(context).textTheme.headline3,
              ),
            ],
          ),
        ),
      ),
    );
  }

We will make simple if conditioning to add a proper margin in our slider.

Next, let’s add the second subtitle which will be the header of the kudos list:

SliverToBoxAdapter(
   child: _buildListSubtitle(context, "BROWSE KUDOSES"),
),

And below add the SliverList, where we’ll build our list:

SliverList(
     delegate: SliverChildBuilderDelegate((BuildContext context, int index) =>
             _buildKudosListTile(context, index),
     childCount: Mocks.recentKudoses.length,
  ),
)

The _buildKudosListTile will build a single kudos tile with all needed data.

Widget _buildKudosListTile(BuildContext context, int index) {
    final Kudos kudos = Mocks.recentKudoses[index];

    return Card(
      elevation: 0.0,
      margin: const EdgeInsets.only(
        bottom: 12.0,
        left: 16.0,
        right: 16.0,
      ),
      child: ListTile(
        leading: Image.asset(
          kudos.sender.avatarUrl,
          height: 50.0,
        ),
        contentPadding: const EdgeInsets.symmetric(
          horizontal: 16.0,
          vertical: 12.0,
        ),
        title: Row(
          children: [
            Text(
              kudos.sender.name.toUpperCase(),
              style: Theme.of(context).textTheme.subtitle1,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: 8.0,
              ),
              child: Image.asset(
                "assets/images/arrow.png",
                height: 12.0,
              ),
            ),
            Text(
              kudos.recipient.name.toUpperCase(),
              style: Theme.of(context).textTheme.subtitle1,
            ),
          ],
        ),
        subtitle: Padding(
          padding: const EdgeInsets.only(top: 10.0),
          child: Text(
            kudos.description,
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
        ),
      ),
    );
  }

6. Animated app bar

And the very last thing in this episode is to create the header which will hide during scrolling the kudos list. For that purpose, we use the SliverAppBar the class provided by Flutter:

SliverAppBar(
   elevation: 0,
   expandedHeight: _deviceWidth * _backgroundRatio + 20.0,
   pinned: true,
   backgroundColor: Colors.white,
   flexibleSpace: FlexibleSpaceBar(
   title: Text("KUDOS WALL", style: Theme.of(context).textTheme.headline1,
   ),
   titlePadding: const EdgeInsets.only(bottom: 8.0),
   centerTitle: true,
   background: Image.asset(
      "assets/images/background.png",
       fit: BoxFit.fitWidth,
       alignment: Alignment.topCenter,
    ),
  ),
),

 

Finally, you have built a fully functional kudos list! Well done.

To learn more about classes used in this article, follow the official documentation:

Link to the final version of the app with implemented kudos wall:
https://github.com/adrkakol/kudify-list

To learn more about uses techniques follow the below links:

Thank you for following this episode of our Flutter tutorial and stay tuned for the next one.

Coding – Adrian Kąkol

Design – Karolina Zawadzka

Video – Robert Fijałkowski

Written by Krzysztof Len
JS Fullstack Gdańsk
Published April 15, 2021