43 Developer Documentation
Petr Vaněk edited this page 2 weeks ago
Table of Contents

Development Environment

The core team uses Debian GNU/Linux + Android Studio, latest version. It is also available for Windows and Mac, but we never used that. You can use any Android device connected to USB to run your code straight from Android Studio on the device. Be aware that you cannot install your own built version and F-Droid's at the same time. You can export and import your database, though.

Tools

You need to install

  • a Java Development Kit (JDK)
  • git
  • adb (Android Debug Bridge, included in Android Studio)

On Ubuntu, you can use following commands (java 9 seems broken, please use java 8)

sudo apt-get install openjdk-8-jdk git adb

Docker

There are also docker images available with all required tools inside.

To start a container with USB connection to a phone, execute e.g.

host $ docker run -it --rm --device=/dev/bus --net=host -v ${PWD}:/src androidsdk/android-30:latest bash
container # cd /src

This mounts the current directory into the container under /src and then allows to build and install the results (see below).

Getting the code

git clone https://codeberg.org/Freeyourgadget/Gadgetbridge.git

(after you did that once, you can use git pull to get the newest Gadgetbridge code)

Alternatively you can use Android Studio to clone the Gadgetbridge repository.

Building and installing the Gadgetbridge apk

If you only want to compile the code, you can simply execute

./gradlew assembleDebug

or

./gradlew assembleRelease

And install it to your mobile or tablet by executing

adb install app/build/outputs/apk/app-debug.apk

Android Studio does all this automatically when you press the Run or Debug button, you may have to open the root directory of the repo for the configuration to be loaded.

Android Studio run button

Short Introduction to Gadgetbridge's Source Code

Important Classes

Overview

Overview UML Component Diagram

All the details about the communication/protocol with a concrete device (Pebble, Mi Band, ...) is inside the "Concrete Device Impl." component, that is, the concrete implementations of the DeviceSupport interface. Only the DeviceCommunicationService has access to those -- clients (typically Activities) talk to the DeviceService interface in order to communicate with the devices.

Bluetooth Error Codes

https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.1_r37/stack/include/gatt_api.h

Logging

We use slf4j for logging, so just use LoggerFactory.getLogger(Your.class) and log away. The output will be written to the Android Log (so you can get it with logcat or Android Studio) as well as to the file /sdcard/Android/data/nodomain.freeyourgadget.gadgetbridge/files/gadgetbridge.log. File logging needs to be enabled in Gadgetbridge's preferences, first.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger LOG = LoggerFactory.getLogger(Your.class);
...
LOG.error("Error accessing database", e);`

Information Display

Use one of the nodomain.freeyourgadget.gadgetbridge.util.GB#toast() methods to display information to the user.

  • the toast when given an exception also logs warnings and errors, so with the toast you not have to use LOG afterwards.
  • can safely be called from a background thread
import nodomain.freeyourgadget.gadgetbridge.util.GB;
...
GB.toast("My toast message", Toast.LENGTH_SHORT, GB.ERROR, e);
My toast message

Database

We use greenDAO for database access. See nodomain.freeyourgadget.gadgetbridge.daogen.GBDaoGenerator for entity definition and generation. Do note that we use greenDAO in version 2, the official greenDAO documentation already mentions version 3.

To add a column to a database, simply add a new field to a particular class in nodomain.freeyourgadget.gadgetbridge.daogen.GBDaoGenerator, then build the project, which will trigger generating of corresponding ...dao.class files. Also, make sure to set a new schema version Schema schema = new Schema(xx... and prepare a migration file in src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/.

Icons

All icons should be provided as vector drawables, do not use PNGs anymore. If you are drawing the original design in SVG, make sure to export as regular uncompressed SVG, because Android Studio handles these files better. Then, import it to Android Studio via right click in Project panel → New → Vector Asset → Local file. Then, use Avocado optimizer for Android VectorDrawable (VD) and AnimatedVectorDrawable (AVD) xml files. Avocado rewrites the VectorDrawable using the smallest number of s and s possible, reducing their file sizes and making them faster to parse and draw at runtime.

Device icons

For device icons (ic_device_xxx, ic_device_xxx_disabled), start from an existing icon's SVG source - you can use the Galaxy Buds icon, here is the source for the normal state and here for the disabled state. Modify this SVG (remove the buds and draw the device you need), save, then import into Android Studio as described above, then optimize with avocado. Then, in Android Studio, change the dimensions inside the XML file to this:

android:width="45sp"
android:height="45sp"
android:viewportWidth="30"
android:viewportHeight="30"

and if you want to optimize it even further, change the strokeWidth to remove unnecessary precision, for example from strokeWidth="0.498675" to strokeWidth=0.5". Look at the other device icons for examples.

Preferences

Preferences that are not specific to the user's device but are for the whole application are in Prefs prefs = GBApplication.getPrefs();.

User's device specific preferences - that is, each devices own preferences - go into SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());

One should try to re-use existing preferences. When adding new preferences, these should be made generic so they can be re-used, rather then being vendor or device specific, if it is not required. So for example RGB color settings can change color on a watch, led, fm transmitter or a wireless ear buds.

Complete rundown of steps of adding a feature

The Adding battery info screen to Gadgetbridge blog post is a friendly documentation of the steps needed to add a new feature to Gadgetbridge and it touches on several important parts - adding a new database table, hooking up device bluetooth events, storing data, adding a chart screen and so on.

Short introduction to git and the related workflow

Git can be intimidating and being familiar with it takes some getting use to. There are numerous resources on the internet, like the Dangit, Git!?! or Git documentation. Here is a short, opinionated, by no means comprehensive guide to the typical steps that are needed when working with Gadgetbridge code and repo. Use this as a simple guide but do make sure to read-up on git more in other places, in documentation and so on. If you spot an issue, please edit it too, to help other to be more confident and comfortable when using git.

Forking the repository

Initial step: fork the Gadgetbridge repo on Codeberg to have your own repo. You do this by using the Fork button on the Gadgetbridge repo page. This will create your copy of the repo in Codeberg, under your username. The username will be unique to you, so in this steps, username indicates a Codeberg and you must replace it with your username if you copy/paste these commands. As this copy is sitting on the remote Codeberg server, we will refer to it as a remote or as origin. In order to work with the code, you will need to make a local copy via cloning:

Cloning your forked copy

To get the code to your computer, you must clone the repo.

git clone https://codeberg.org/username/Gadgetbridge.git

This will create local copy of the repository. The remote copy will be named origin, while the official Gadgetbridge repository is registered in your cloned repo as upstream.

Master branch

The main branch of the Gadgetbridge repo is called master.

Creating a branch

When adding a feature for later merge/pull request, you typically create a branch. You do a branch and do not do this in the master, because you will typically like to keep the master as is, in order to be able to have it to follow the upstream's master in Gadgetbridge repo. You can either add a branch before you start:

git checkout -b new-branch

Or you can first make some changes, and only then make the branch, for example like this:

  • do some edits
  • git add ./path_to_the_changed file(s)
  • git checkout -b new-branch

Committing into the branch

Your changes are now being stored into your my-new-branch by committing:

git commit

Pushing your local branch to your remote on the server

git push origin new-branch

Switching branches

As long as all your changes are committed, you can switch between different branches, like this:

git checkout master in order to perhaps see how things are in the master branch and then you can go back to your new-branch git checkout new-branch.

Seeing changes between branches

You can see diffs between your new-branch and the master: git diff master

You can also get the master version of a file you edited, to roll it back to the "original" state: git checkout master ./path to a file

Syncing with the Gadgetbridge project

This is all cool, but while you work on your thing, the Gadgetbridge project is moving along and you must stay synced to it. You do this by switching to the master branch and pulling the remote changes:

  • git checkout master
  • git pull upstream master

This updates your local master to be the same as upstream.

Rebasing on top of the master

You must also ensure, that your branch is actually based on the master. You do this by rebasing on top of the master:

  • First, switch to your branch: git checkout my-branch
  • Then "rebase" it on top of the remote master: git rebase upstream/master

Resetting the master to the upstream

Sometimes, you mess things up badly and want to make sure that your local master is really the same as the upstream master. This can be done by using the destructive reset command of git. This will cause local data loss, so be sure to know why you do this.

  • You switch to your master: git checkout master
  • Remove all unadded files: git clean -f -d
  • And then reset it to upstream: git reset --hard upstream/master --

Squashing commits via git rebase

The git rebase command is very power full and allows you to do many things, like remove, re-order or squash commits, edit the commit message and so on. Read-up about it in the documentation. One of the things it can do is to allow you to selectively squash commits. This can be done in an interactive way by using the -i option and choosing a commit where you want to start. As the action of using the rebase still makes a commit, so somewhat counter intuitively you must choose "one commit before the start" of your commits:

git rebase -i xxx-one-before-the-start-of-your-commits

In the text editor that is opened for you, you leave the first line intact and edit the pick word in front of the commits. For example by changing the pick to squash (or to s), this commit will be squashed to the one above it.

Force pushing

As the above-mentioned rebase actions overwrite git history, if you have previously pushed to your remote, you must force push now. You can only do this for your private work and should not do this if you share the repo with other people, because this breaks things for the (again, read-up about it). But for your work and/or while working alone in a dedicated branch, this is OK. You can also do this even if this branch is used as a pull/merge request.

git push -f origin new-branch