A Kotlin/Native Ecosystem to prevent package pilfering
A Porch Pirate is someone who steals a package which has been delivered from the porch of someone who is not home. An example of this can be seen in one of my favorite YouTube videos ever, Package Thief vs. Glitter Bomb Trap.
Unlike the YouTuber who made the focus of his innovation Utterly Delightful Revenge, I have never worked at NASA, so my level of innovation went more to the "stop packages from getting stolen" side of things.
This project is designed to work with a prototype wooden box which will eventually be evolved into some kind of metal box you can bolt to the side of your house. An iOS and Android app will talk to a server, and the server will send commands and receive messages from a Raspberry Pi attached to a servo motor which can lock and unlock the box.
This project will use Kotlin/Native to allow code to be shared across all four software parts of the project (iOS, Android, Server, and RPi).
Kotlin/Native and Kotlin Multiplatform projects are ways that JetBrains, the team behind Kotlin, have created to allow you to use Kotlin across multiple platforms.
Kotlin was originally built to work with only the Java Virtual Machine, but JetBrains have also built a compiler, known as Konan, that outputs LLVM-intermediate representation bytecode, which allows code to be handed to the LLVM compiler so it can be compiled in many of the places where LLVM allows code to run.
Specifically, this includes iOS, Mac, Windows, and assorted flavors of Linux (including Raspberry Pi).
A multiplatform project is a project which allows you to build shared code for both JVM-based projects and Native projects (as well as Javascript projects, but we won't be working with that here).
Kotlin/Native and Multiplatform projects are still in beta. Things are going to break. This is a super-exciting ecosystem and there's a lot that works well, but there's a lot that doesn't, or can be broken at any point. Please bear that in mind as you think about writing something with this.
There are (going to be) five primary parts of this application:
PPPShared
, the shared framework used across all platforms- An Android app, built in Kotlin, with the
PPPShared
module as a local dependency. - An iOS app, built in Swift and which leverages an Objective-C framework built by Kotlin/Native as a dependency.
- A ktor server, which runs on the JVM in a Docker container, and which uses the
PPPShared
module as a local dependency. - [NOT BUILT YET] A small app that will allow the Raspberry Pi to pair with a device, listen for commands from the server, and send results back to the server.
Folder structure in Gradle projects is extremely important - it helps define where things are placed so that you can reuse them. Here's a general overview of what the folder structure looks like:
Note: For android
several things are in java
folders, but they actually contain Kotlin code.
code
build.gradle // basic gradle information for the entire project
gradle.properties // Definitions of things used across all gradle files in the project
settings.gradle // General settings for entire project
|- android
build.gradle // Android app-specific gradle file
|- src
|- androidTest
|- java // Tests which require Android elements
|- main
|- java // main code for the Android app
|- test
|- java // Tests which only require the JVM
|- iOS
|- PorchPirateProtector // Main Swift code for iOS app
|- PorchPirateProtector.xcodeproj // Open this to see the iOS app
|- PorchPirateProtectorTests // iOS-specific Swift tests.
|- PPPShared
build.gradle // Gradle file for everything in the shared lib on all platforms
|- src
|- androidMain
|- kotlin // Library Platform-specific implementations for Android
|- androidTest
|- kotlin // Test platform-specific implementations for Android/JVM
|- commonMain
|- kotlin // Shared Kotlin code across JVM (android, server) and Native (iOS)
|- commonTest
|- kotlin // Shared test code across JVM and Native
|- iosMain
|- kotlin // Library platform-specific implementations for iOS
|- iosTest
|- kotlin // Test platform-specific implementations for iOS/Native.
|- server
build.gradle // Gradle file for server-specific setup
|- scripts // Some helper scripts I made because Docker syntax is annoying
|- src // Server application source code
This project uses one of the many Model-View-PleaseDearGodAnythingButTheModelOrView
architectures which proliferate on mobile development platforms, specifically Model-View-Presenter
.
This architecture was selected for a couple of reasons:
- It is the same architecture the KotlinConf app uses, which is a very, very helpful reference
- Most business and data logic becomes centralized in the
presenter
object, which can be shared across platforms - A "view" is actually just a Kotlin
interface
, meaning that each platform has to implement its own version of what a view is.
On iOS, the items implementing the view
interface (translated into an Obj-C protocol) tend to be UIViewControllers
. On Android, they tend to be Fragments
(sorry, Jake).
While testing, you don't even need to have a view
that does any kind of drawing on the screen. You can simply create a class which implements the view
protocol and has backing variables you can check to see if various methods got hit. This makes it possible to run tests without the iOS and Android runtimes (and therefore run way faster than UI tests).
In order to run all elements of this project you will need:
- A Mac (sorry, can't compile iOS on anything else)
- Xcode
- Android Studio
- Docker for Mac
To run the Common tests, you need to run PPPShared
's androidTest
task, you have a couple options. You can either use the Gradle sub-window of Android Studio and double click under code > PPPShared > androidTest
to run the android tests.
If you prefer the command line, from the root of the project you can run:
./gradlew PPPShared:androidTest
Through either method, results of your tests will be output to code/PPPShared/build/reports/tests/androidTest/index.html
. Note that this file is not checked into version control, so it won't appear on this repo, only locally.
NOTE: Currently this is not working with Instant Run, working on whether there's any way around that. In the meantime, to run the Android app, go to `Preferences > Build, Execution, Deployment >
In Android Studio, you should have an app
configuration created by default when AS recognizes that the project contains an Android application target.
If this does not get created, go to Edit Configurations...
, add a new Android app configuration, and set it to use the android
module. Once that's set up, you should be able to use the Run button in Android studio to run the application.
In iOS, select the PorchPirateProtector
scheme and hit the run button. If for some reason the system decides to get in a loop where it can't find the iOS binaries, go back to Android Studio and run the packForXcode
task under code > PPPShared > other
or run:
/.gradlew PPPShared:packForXcode
at the command line from the project root.
First, make sure Docker for Mac is running. Otherwise the rest of this will faill spectacularly.
// TODO: Automate most of this with a docker-compose.yml
file.
NOTE: The setup only has to be done once. Once you've setup your database, you just need to run the convenience shell script to start your database before starting the server.
Next, you'll need to make sure you have a docker image that will serve up MySQL 5.7.24. Follow the instructions on the MySQLServer DockerHub page to get the 5.7.24 image installed - make sure you set up a root
user with a proper password before proceeding.
Once that's installed, run the convenience docker_mysql_start.sh
script (you may need to make it locally excecutable) to start that container, run it headless, and print out its current information.
Grab the IPAddress
from that and make sure it's the same one as in the Database.kt
file - otherwise you'll need to update that file. You will need to get into the container and make sure you've got a database which matches the db name in that file, and a user which matches the DB user in that file.
To do that, you'll need to get into the container's shell. To do so, run:
docker exec -it mysql1 /bin/sh
You'll need to make sure that you have a MySQL user setup with a database name, username, and password matching those in the file on that database.
To get into the MySQL command line once you're in the container's shell, run:
mysql -u root -p
and hit enter. You will be prompted for your password. Once you're in to MySQL, add a new database:
create database ppp;
Next a new user which will allow you to access the database you just created with password authorization, with username and password matching those in Database.kt:
GRANT ALL PRIVILEGES ON ppp.* TO 'ppp-database'@'%' IDENTIFIED BY 'password';
You should then flush privileges to make sure changes are properly applied:
flush privileges;
Next, you can leave the MySQL command line by typing
exit
At this point, you'll be back in the container's command line. You can also leave this by typing:
exit
Run the docker_rebuild.sh
script in order to create a new jar
based on the code in server
, and then to create a docker image with that jar
.
When that completes successfully, you can run docker_run.sh
to start the docker container.
Note that anytime you make a change to the code in server
, you'll need to run
docker stop ppp
in order to stop the running container, then rebuild and rerun the server to allow your changes to apply. You do not need to rerun the MySQL server.
After all that, in a terminal window on your Mac, run:
docker ps
This will print the status of all your currently running containers. You should see the two containers, ppp
(containing the server) and mysql1
(containing the database) in the list.
NOTE: The way this is set up right now, you can only access things via
localhost
. If you are better at dealing with Docker crap than I am, you should be able to get this deployed as a container...somewhere, and then point to your deployed container in theAPI.kt
file.