Dogly - a mobile app with Flutter: overview and back-end service

7 minute read

Introduction

Hello, long time no see. This post is the start of another series on the blog. I will create a simple end-to-end mobile app from scratch. It is not connected with the data world at all. I just would like to challenge myself and check if I can pick the new technology fast. The second reason is a need for front-end technology in the skill set. That’s why I choose Flutter as a framework for my app. It will be an Android-first app, but Flutter supports also iOS, web, and desktop, so I hope it will be also useful for future data-related projects. The application name is Dogly. The goal of the application is to help the user with the dog care. The user should be able to keep track of his dog’s health, meals, walks, etc. The application won’t be a piece of art, it will have a rather simple UI, but every part should work correctly and meet all of the assumptions. In this post, I will show a brief overview of the project plan and the process of setting up the backend service - Appwrite.

Let’s get to work!

App features

Here is a brief and illustrative list of features that the app should offer for the user:

  • account (authorization, authentication),
  • user profile customization,
  • adding a dog with photo, name, breed, size, and date of birth,
  • calculating dog years from human years,
  • displaying a random fact about the dogs,
  • adding a walk with the time and number o steps or kilometers information,
  • adding a visit to the vet with info,
  • calendar with all events applied,
  • dark mode.

I think such an app could have a lot more features, but I will probably create just an MVP version here. For example, it could also have features like notifications or sharing data with other users. Coding and describing the app store-ready full version of the app would be a time-consuming process and I think a quite big project for one person. The list can change or grow up during the process of creating a project. Looks like it will be a quite long journey anyway (5-6 blog posts I suppose), but the learning outcomes should be big enough.

Tools

As you already know the framework I will use to create the app is Flutter. Flutter is an open-source app development platform developed by Google. It uses the programming language called Dart. It works in a declarative way, the central idea is to build the UI using widgets. Declarative means that Flutter rebuilds the UI when the state changes.

The state is information that needs to be managed in the most approachable way in the code base. My choice is Riverpod - a reactive caching and data-binding framework. It is a better version of the Provider - the state management package recommended by Google in their tutorials. I choose it because I already know Provider and its weaknesses, so the knowledge gap to be filled for Riverpod will be much smaller than learning other frameworks like a BloC that uses a quite different approach for state management.

The tool I would also like to mention is Appwrite. It is a backend-as-a-service platform that provides developers with all the core APIs required to build any application. I choose it over Firebase - the most popular platform, because Appwrite is open-source and I can just self-host it. I just always prefer open-source software. It will help me with authentication, databases, and object storage. I will also use Isar as a database to persist application data for unauthenticated users.

All of the graphic assets will be generated using a paid version of Midjourney. I will also use

Obviously, I will also use a lot of third-party Dart packages, but here I wanted to show the most crucial tools to build my app.

App architecture

I would like to keep the app code base scalable. It should be the same easy to maintain at the end of the project as at the beginning. I will try to follow the architecture called Riverpod Architecture, pretty borrowed from CodeWithAndrea website (which is an amazing source to learn from btw). I think I won’t be able to keep it perfect, there will be some compromises. However, I hope I will be able to learn a lot of patterns during the process of creating Dogly. I would also like to keep the feature-based alike directory structure. Every feature directory will contain the following directories:

  • presentation:
    • controllers - objects responsible for controlling widgets’ behavior,
    • widgets - certain UI elements connected with the feature only, for example, form field with the password input,
    • views,
  • data - repositories responsible for connecting with the backend like databases, authentication services, etc,
  • domain - objects representing entities in the app,
  • application - optional directory with code controlling shared logic between multiple widgets and repositories, for example managing authenticated and unauthenticated user cases.
  • core, commons, and constants - they should contain various things shared across the app.

I just hope I won’t get lost in my own code and the code base will look as planned at the end.

Appwrite

Appwrite is mainly designed to be self-hosted. There are various options to install it, you can read about it here. I will use the docker-compose to deploy my instance of the Appwrite. The docker-compose.yaml and .env files are quite long, so I won’t paste them here. The only difference in my implementation is the used web server. The example file uses the Traefik. I use Caddy daily, so I just need to change the networks section to:

networks:
  caddy-network:
    name: caddy-network
    external: true
  appwrite:
  runtimes:

All of the Traefik configuration should be also removed. My Caddy implementation can be checked in one of the previous posts. It is also important to change some environment variables for production deployment. Here is a brief guide. Personally, I just created an encryption key and configured the SMTP server, but there are also some hints for deploying in a cluster or configuring good backups.

The playbook for deployment with Ansible looks like this:

# deploy_appwrite.yaml
---
- hosts: data_master_nodes
  vars:
    docker_compose_dir: /docker/appwrite/

  tasks:
    - name: Copy compose source template
      template:
        src: ./composes/appwrite/docker-compose.yaml
        dest: "{{ docker_compose_dir }}"
        mode: 0600
    
    - name: Copy .env
      template:
        src: ./composes/appwrite/.env
        dest: "{{ docker_compose_dir }}"
        mode: 0600

    - name: Build appwrite
      community.docker.docker_compose:
        project_src: "{{ docker_compose_dir }}"
      register: output

Let’s run it with the command:

ansible-playbook deploy_appwrite.yaml -u charizard

Appwrite offers also a paid hosted version. You can check it here.

Now the Appwrite console can be accessed on the localhost or the specified domain. After the initial configuration like organization or account, I need to add the ‘Dogly’ project with a Flutter platform. For Android, the app name and the package name should be provided. To capture the Appwrite OAuth callback, the following activity should be added to the manifest:

<manifest ...>
  ...
  <application ...>
    ...
    <!-- Add this inside the `<application>` tag, along side the existing `<activity>` tags -->
    <activity android:name="com.linusu.flutter_web_auth_2.CallbackActivity" android:exported="true">
      <intent-filter android:label="flutter_web_auth_2">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="appwrite-callback-[PROJECT_ID]" />
      </intent-filter>
    </activity>
  </application>
</manifest>

The Appwrite Flutter SDK can be easily installed in the app with the Dart package manager:

flutter pub add appwrite

Now let’s create the database with collections. It can be done from UI or using one of the server SDKs. I should have the following collections with the attributes:

users:

  • name (str)
  • e-mail (str)
  • profile picture (str)

dogs:

  • user id (str)
  • name (str)
  • date of birth (datetime)
  • breed (str)
  • size (str)
  • picture (str)

walks:

  • user id (str)
  • dogs ids (array[str])
  • start time (datetime)
  • end time (datetime)
  • steps (int)

meals:

  • time (datetime)
  • meal (name)
  • dog id (str)
  • user id (str)

visits:

  • time (datetime)
  • name (str)
  • note (str)
  • dog id (str)
  • user id (str)

The database uses the document model so I won’t create any join table to manage the relationship between walks and dogs (multiple dogs can attend in the same walk). I just store the ids in the array attribute of the dog entity. I know that Appwrite offers the relationships feature now, but it was not a thing yet at the time of creating the app. It is also important to give the proper permissions to the collections, so users can create, update, read, and delete documents.

Conclusion

Deploying and configuring the Appwrite service is not so hard. Obviously it is not that advanced like the Firebase, but I think it is much more interesting to use the self-hosted open-source software than the tools from big tech companies. The backend with the database for the application is ready. I will start connecting the app to it in the next post. I hope I will make at least a few things in a good way. Wish me luck. Cya!

References

  1. https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/
  2. https://appwrite.io/docs