An Android client for beehive scales. It reads the weight, temperature, humidity and air pressure that sensors in the hives write to a server, shows each hive's latest reading next to how far it has moved since the one before, and raises a notification when a hive's weight changes by more than a threshold the beekeeper sets.
| Overview | Hive detail | Diary |
|---|---|---|
![]() |
![]() |
![]() |
The screenshots are from the April 2025 checkpoint build and predate the colour scheme the app finished with.
BeeSense was written for the Mobile Application Development course at the University of Žilina during the summer semester of 2024/25, and submitted in June 2025.
The hardware came first. As a secondary-school year project I built scales that sat under hives and measured weight, temperature and humidity, and sent the readings to ThingSpeak. ThingSpeak plotted them and did nothing else, so this app is the other half of that idea: keep the readings in a database, and put in front of them a phone screen that shows what has changed since the last look and lets the beekeeper write down what was done about it.
It is a single-user tool. There are no accounts, no roles and no sharing, because there was only ever one beekeeper.
The server is not in this repository. It is a PHP script in front of a MySQL database, offering three read-only endpoints: the last row of every hive table, the last two rows of one hive table, and the last N days of one hive table. The app never writes to it. The interface is in Slovak throughout.
The overview is a two-column grid of hives, each card carrying the hive's name, its total weight and its inside temperature, with an arrow beside each value showing which way it moved. Tapping a card opens a dialog with the readings the card has no room for: the left and right scale halves separately, outside temperature, humidity and pressure. A search field narrows the grid by hive name or ID, and a heart marks a hive as a favourite for the length of the session.
The graph screen takes a hive, one of seven quantities and a period, and draws the history as a line. The fixed periods run from a day to a year, and there is also a custom range, which is described under Limitations. A year of readings is far more points than a phone-sized chart can show, so above a hundred points the view model averages them in fixed-size chunks, more of them the longer the period, and the chart itself never shows more than fifty points at once.
The diary records what was done to a hive. An entry is a type chosen from feeding, treatment, honey collection, inspection or other, a timestamp, and a free note. One search box matches against all three fields at once, so a word from a note and a fragment of a date both find their entries.
Hive management is where the app is told which hives exist and what each one measures. Settings hold the theme and the notification thresholds, and live in one row of the local database.
| Layer | Technology | Version |
|---|---|---|
| Language | Kotlin | 2.0.0 |
| UI | Jetpack Compose, Material 3 | 1.7.8, 1.3.2 |
| Navigation | Navigation Compose | 2.8.9 |
| HTTP | OkHttp | 4.12.0 |
| JSON | Gson, with org.json for the sensor payload |
2.10.1 |
| Local database | Room, via kapt | 2.7.0 |
| Background work | WorkManager | 2.10.1 |
| Charts | MPAndroidChart, from JitPack | 3.1.0 |
| Build | Android Gradle Plugin, Gradle wrapper | 8.8.2, 8.10.2 |
| SDK | compileSdk 35, minSdk 24, targetSdk 35, JVM target 11 |
There is no dependency injection framework. AppContainer is a hand-written service locator holding the Room database and the three repositories built on it, and view models that need more than an Application get a ViewModelProvider.Factory written by hand.
Each hive has its own table on the server with three columns: an ID, a timestamp, and a data column holding a JSON object such as {"humidity": 68.25, "pressure": 1013.75, "weight_left": 12.4, "weight_right": 12.1, "temperature_sensor": 34.2, "temperature_outside": 26.7}. Adding a quantity to a scale later means adding a key, not migrating a table, which is why the schema is shaped that way.
The cost lands on the client. Nothing in a response says which quantities a given hive reports, and the decoder reads each key with optDouble(key, 0.0), so a sensor that is not there and a sensor reading exactly zero arrive looking identical. Every hive therefore carries six booleans recording which sensors it has, and those booleans decide what the overview dialog draws and what the graph screen offers.
Those booleans are typed in by hand, so the hive editor refuses to save a set it has not checked against the server. On save it calls cmd=all_tables_last_row, finds the table the user named, and confirms that every ticked sensor has its key present in that table's most recent reading. A misspelt table name, or a sensor the hive does not carry, is rejected while the user is still looking at the form rather than turning up later as a flat line along zero on a graph.
TrendAnalyzer compares the newest reading against the previous one for each quantity and classifies the difference against two thresholds rather than one, so the arrow separates drift from an event. Weight moves at 0.2 kg and again at 2 kg, temperature at 0.5 °C and 5 °C, pressure at 1 hPa and 5 hPa, humidity at 2% and 10%. The second threshold turns the arrow red. A hive with no previous reading, or a quantity the hive does not report, comes back as unavailable and draws nothing at all, which is a distinct state from stable.
HiveMonitoringWorker is a CoroutineWorker enqueued as periodic work with a network constraint. Each run reads the settings row, lists the hive tables, pulls the last two readings of each, and sends a notification for any hive whose absolute weight change meets the threshold in settings.
A notification is not sent at a fixed loudness. The worker keeps the time and level of the last notification for each hive in SharedPreferences, so a hive that has been quiet for longer than the reset interval starts again at informational, while one that keeps tripping the threshold climbs a step towards warning and then alert, which changes the title and the notification priority. The bug in this ladder is noted under Limitations.
The manifest removes WorkManager's automatic initialiser with tools:node="remove", and MainActivity implements Configuration.Provider and calls WorkManager.initialize itself, which is what lets the app supply its own WorkManager configuration instead of the default one.
Room holds three tables: the hives and their sensor flags, the diary entries, and a single settings row fixed at ID 1. Readings are not among them. Every screen fetches from the network when it opens, so an app with no connection still shows the hive list and the whole diary, while the overview and the graph come up empty.
The project opens in Android Studio and builds with the Gradle wrapper. Nothing has to be configured first:
git clone https://github.com/Miclah/BeeSense.git
cd BeeSense
./gradlew assembleDebugThe API URL and key are constants in ApiService.kt and HiveEditorViewModel.kt, and are placeholders in this repository, so pointing the app at a server means editing those two files.
The server this app read from is no longer online, so a build installed today reaches every screen and finds no measurements on any of them.
The custom date range on the graph screen is a stub: the pickers store the dates and the loader ignores them, fetching the last 30 days whatever was chosen.
The date picker calls context as FragmentActivity, and MainActivity extends ComponentActivity, which does not satisfy that cast.
The SQL management screen is a placeholder that draws its own title and nothing else, and no route in the app navigates to it.
The notification severity ladder cannot escalate on the shipped defaults, because the level resets after four hours and only escalates after five.
The monitoring interval is set to one minute, which was for testing and which WorkManager clamps to its fifteen-minute floor for periodic work.
Room is configured with a destructive migration fallback and defines no migrations, so a schema change discards the hives, diary and settings on the device.
Diary entries are ordered by a dd-MM-yyyy string in SQL, which sorts correctly within a month and not across one, and they are not attached to a hive.
Every string the user sees is a Slovak literal in the composable that draws it; strings.xml contains only the app name.
The two files under src/test and src/androidTest are the stubs Android Studio generates, so the project has no tests.
data/repository/HiveRepository.kt, which falls back to eleven hardcoded readings when the API returns nothing, and data/repository/HiveManagementRepository.kt, which keeps hive configuration in SharedPreferences, are both unreferenced and do the jobs that data/db took over.
ApiService reads the API key from assets/api_key.env and falls back to a constant when that fails, and the assets file was never added, so the constant is what the app has always used.
The release build type is unsigned and has minification disabled, and proguard-rules.pro is the untouched template.
Apache License 2.0. See LICENSE.


