This is a short article to document a process of adding a fairly simple feature to Gadgetbridge. This article is intended to newcomers wanting to participate in Gadgetbridge development who do have some understanding of programming in Java for Android (in Android Studio). Written by a fellow novice programmer in the Android Java ecosystem, it hopes to serve as a basic introduction to some of the concepts used in the Gadgetbridge codebase. There is more information in our wiki, like the Developer documentation and if you are looking for an advice how to add a new device support into Gadgetbridge, there is a comprehensive New device tutorial.

Introduction

Gadgetbridge has been showing the wearable device's battery level and low battery warning almost forever since it's beginning. What we want to add is a screen showing us a graph of battery level over time + maybe even some more known details about the battery itself if the device provides it.

First, we need to do some digging and find out how Gadgetbridge knows about device battery stats. This is the first biggest hold-up, because getting familiar with the codebase does take some time but is an essential part of the process. Do spend some time digging through the code to understand the patterns, packages and code layout.

Hooking into device events

Where do we begin... searching in files Ctrl-Shift-F for battery sounds like a good place to start for us, but it is a bit too vague, so we try searching for battery low, since we know this can come up in the notification. We get some strings in the strings.xml, so let's find a usage of one of them, notif_battery_low_title via same global search in files. One of the places where this is used is handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent) in AbstractDeviceSupport. This seems to be a good hit, because if we look at usage of this method via Ctrl-B, we can see it being used in most if not all of the implemented devices. This final method is a good place where we can catch the event of getting battery info as it is received from the device. Here, we can hook our routine to store this data into database. We can also note and explore a bit the GBDeviceEventBatteryInfo class used in the handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent), as it provides more details about the battery, if a particular device supports that, for example number of charges, last charge time and so on.

Database

To store the data, we need a database table. In Gadgetbridge, database access is provided via greenDAO - Object Relational Mapper for Android. Tables are defined in src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java as entities. In the GBDaoGenerator we define our entity, containing properties, which are mapped to database columns. To store different data per device, we must ensure to add mapping to our devices with the addToOne(device, deviceId). The above defined entities will be auto-generated in the nodomain.freeyourgadget.gadgetbridge.entities package. Experiment as needed but make sure to test your object model well and check logcat for error messages, because the DAO can insert values to the database but still throw errors if for example indexes are not set correctly.

After the table definition is done, we must also bump the database schema version: Schema schema = new Schema(VERSION, MAIN_PACKAGE + ".entities");, this is important for database migrations.

Activity

At this point, we are getting our battery data, are storing it into database and would like to show a line chart showing battery level over time. To define a new activity (screen), it is important to extend the AbstractGBActivity because it will taking care of the theme (light and dark) language switching and so on.

Charts

Gadgetbridge has several places where charts are already used, for that the MPAndroidChart library is being utilized. Some convenience classes are already defined and should be reused, like the AbstractChartFragment, which is especially useful if you want to show activity data like steps or heart rate.

Logging

When displaying log messages, never use the e.printStacktrace(). Either use the predefined logger private static final Logger LOG = LoggerFactory.getLogger(Your.class);, or you can also use the toast GB.toast(getContext(), "Error doing xyz: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);, this will show the toast, log the stacktrace in logcat and write the logs (if enabled).

General advice

  • Make sure to auto-format the code you write, but leave existing code as is, even if not formatted well, because formatting it would break existing PRs and it also destroys information about the original author.
  • There are many convenience and utility classes already defined in Gadgetbridge which should be re-used instead of defining again (for example from the nodomain.freeyourgadget.gadgetbridge.util package).
  • Abstract classes definitions are very commonly used throughout and should also be used where useful.
  • Pay attention to the warnings and suggestion Android Studio provides, in general code and also in activity layouts.
  • Try and test your work diligently - there is no bug tracking code in Gadgetbridge and if the app has issues or randomly crashes on some user devices, there is no way to learn about it, unless people make the effort and report it. If you have multiple Android phones, ideally test on them, to see how different Android version behave, what different screen sizes and pixel densities look like.
  • Also, do not forget to test in both light and dark theme.

Conclusion

So this is it, a quick introduction to a very small portion of Gadgetbridge internals. And where is our Battery info screen? Well, the initial code is here and a picture is below, but it needs much more work, like better axis formatting, moving between days... because like always, the small fine details take 80% of the real work. I will keep working on it and it will be released when ready :)