Snapping PhotoNoteBook: Lessons from the kitchen table, or: How an Android app got converted to a Linux snap
This article was written by Alan Watson, with only small cosmetic changes from me. Having stumbled upon PhotoNoteBook, I asked Alan to write a guest blog post and share his project with the wider snap developer community.
The idea for PhotoNoteBook was seeded in 2018 after watching a Double-Glazing salesman measure my windows. He would make a pencil drawing of the windows, and make notes as to the sizes of the panes. I thought: “In the age of smartphones and tablets, that’s ridiculous!”
Later in the year, my partner and I were on holiday. She spotted a flower on a cliff side, and not knowing what it was, she took a photo with her smartphone so she could look it up once we returned home. But then, one month later, she could not remember where the photo was taken. I felt: “What a pity she could not have made a quick note of where she was, and have the date and time recorded.” The idea for PhotoNoteBook was born!
The Android Version
Having not programmed in anger for some years, I had to come up to speed with the “modern” way of doing things. Twenty five years previously, I had produced industrial monitoring and control software for Windows 95 PCs using Borland Delphi (a Pascal-like Object-Oriented programming language) that had its own IDE. For Android software, Google advocated Android Studio (a clone of IntelliJ IDEA), and Java as its preferred programming platform.
Compared to earlier IDEs I had used, it was and remains a sophisticated combination of tools for software development. It took me about a year to learn the fundamentals of the Java language and the intricacies of the Android OS, and during that time, the shape of PhotoNoteBook formed, and the functionality it provided took shape.
Essentially, the idea was simple – take a photo, add a note, and store them so that the image and the associated note can be displayed together. Simples!
The key benefits of the Java language soon became clear. Libraries. Lots and lots of libraries, free and easily available on the Google Java libraries site. Some of the really specialist stuff, e.g.: database interfacing and JSON processing was easily available and configured in the build using Gradle. I could concentrate on user interaction and experience, and the use-case support, while some of the really tricky stuff was handled in the libraries. Great!
As it was developed, it became clear to me that PhotoNoteBook was growing into something more than just a photo-and-note-taker. With a few extra screens and some sophisticated processing, it could grow into a complete photo and image library manager. Processing was added to manage multiple collections of PhotoNoteBooks (PhotoNoteBook became the name for a group of related photos and notes, and the name for the app), as well as functionality to import and export PhotoNoteBooks to and from cameras and storage devices, and to backup and restore individual PhotoNoteBooks without losing the relationship between the images, the notes, and the collection(s) they belonged to. The first release of the PhotoNoteBook app went live on Google Play in September 2018.
Image of PhotoNoteBook running on an Android tablet.
Transition to a PC-based app
Although I could manage my entire digital image library – around 12,000 images – using the PhotoNoteBook App on my Android 8-inch tablet, it soon became clear that there were some issues that I had not considered, not least being the fiddly nature of the tablet keyboard when entering even small amounts of text. The thought of annotating 12,000 digital images on a touch keyboard was to say the least daunting. And so the solution was obvious – write a PC version.
Some outline design requirements were obvious: it had to look the same as the Android version, with similar functionality and more. It also had to interoperate with the Android version – PhotoNoteBooks backed up from one platform had to be capable of being restored to another.
Options for the Platform – Why Ubuntu, why JAVAFX?
The choice of Ubuntu was simple – it had become my desktop of choice as I phased Windows out (except as necessary for my employment), and phased in Ubuntu for home use. Using Ubuntu as an OS platform was a no-brainer, however I still had Windows at the back of my mind – there are a helluva lot of Windows users out there.
However, not being much of a systems person, there were a couple of things that scared the hell out of me – not least the issue of “dependency hell”. I have no idea what goes on under the Linux/Ubuntu bonnet (I still don’t), so the thought of coming to grips with the complexities of Linux and its libraries didn’t appeal to me. Furthermore, looking at distribution packaging approaches such as deb added to my concern.
A further concern was that, after investing a lot of time understanding the Java programming language, the only way to go was Java, and implementing a Java program into an unknown target environment had risks. I had to find a way to control both the OS environment and the Java environment to ensure the software would not fail due to environment and system mismatches.
A LOT of background work went into this phase. The first task was to select an IDE: I looked at Eclipse and JavaBeans, but settled on IntelliJ Idea Community Edition. Its advantages were that it was a) free b) looked and operated just like Android Studio, and c) although I didn’t realize the importance at the time, it had JavaFX plug-in and a Jlink plug-in.
The first break-through was my discovery of Jlink. This is a cool tool that pulls in all of the Java support environment needed to run on the OS the application is being built on, and delivers it as a runtime image that can be deployed onto the target OS.
It even creates the startup scripts required to run the program and configure the Java JVM. The deployable image and its Java runtime environment are self-contained. For the Windows OS, this means it has all of the dynamic runtime libraries (DLLs) required to to provide common functions and interface to the Windows OS. For Linux, it means that the required dynamic libraries (.so) are included in the runtime image.
That still begged the question of the development platform. As mentioned before, Java was a no-brainer. The key decision was around the client interface framework: the choice was between the elderly Swing framework, or the newer and likely-to-replace-Swing JavaFX.
A close examination of the JavaFX framework revealed another no-brainer decision, and I went with the (then) latest LTS versions of OpenJDK and OpenJFX. The community editions are free, although official support is patchy. At the time of writing, both were free to use in commercial products.
The PC program development started by first identifying what I could reuse from the Android version, which was a lot more than I anticipated. The major changes were (unsurprisingly) in the user Interfaces; those had to be completely re-written. File access is, of course, a lot simpler in the OpenJDK environment – Android has a fairly difficult “contract” system for write-access outside of an apps-managed data container. The backup and restore routines were almost completely portable, except for the difference in background thread handling for the low level IO routines. Google’s GSON-JSON library (that I used in the Android version) slotted right in.
I found many helpful Java libraries in the Maven repository for image processing and database interfacing, all of which were easy to configure in the single ”build.gradle” configuration file. The Intellij Idea IDE is very configurable and allows for the building of Java software packages, which can include “sub-packages” for the easy organization of classes.
Similarly, the use of the JavaFX framework is extremely simple, and is based upon the concept of ”Stages” and ”Scenes” – essentially the Stage shows a Scene, and the Scene defines what is shown on the Stage! Change the scene, change what’s seen. Geddit?
It was quite easy to define the contents of a Scene by creating classes based on the JavaFX library components, as well as defining the spatial relationships between those components. This can be done in code (as I prefer), or using visual-design packages to produce fxml definition files. Not being a fan of visual-design packages, I stuck to hard coding the scenes, but one “new” innovation I adopted was the use of Cascading Style Sheets (.css) files to define some of the finer details of my visual components. It is so much easier to get consistency of component visual elements using CSS, and even a newbie like me got hold of it very quickly. Even better, by defining multiple stylesheets and coding in a change routine, it is possible to create different themes for the application.
Options for App distribution
As the months went by, development proceeded, but the issue of distribution and library management hadn’t gone away. I knew about snaps so I investigated further. I found the documentation on the Snapcraft site confusing and sometimes with maddeningly circular references, however I managed to boil the information down to something I could get my head around.
The authors do seem to have made some basic assumptions about the knowledge of Ubuntu internals, but hey, I’m an app programmer and I don’t really have time for that sort of thing! However, it did start to make some sort of sense, and I began to realize that it might not be as complicated as I initially thought.
Snapping the Snap!
Understanding the security model was the first task. Understanding how data was encapsulated in a protected container was made easier by my understanding of the Android container model, and in fact, it works well with the approach to data and file management approach used in PhotoNoteBook, i.e., all of the photos, notes, database and control files are preserved within the application’s environment.
I quickly realized that a fully confined running environment was the best way forward, but that gave me some concerns until I got my head around the idea of plugs – quite difficult as the explanations given in the Snapcraft documentation at the time were not exactly totally transparent (sometimes technical authors really have to explain what they think is blindingly obvious). It also took me a while to realize that extensions could remove some of the hurdles of defining plugs and packages, yet I am still not entirely sure that I am not overspecifying information in the final version of my snapcraft.yaml.
The sequence of build events was quite simple: first build your code in the IDE and test. Then, Jlink the codebase. This produces the runtime image complete with dependencies and a Java runtime environment. I then copy this image to where the snapcraft app can get at it to make the snap using the snapcraft.yaml definition file. I then test this to death, and finally upload/download via the Snap Store to ensure that the new version installs cleanly over the old one, with no loss of app data.
The snap version up and running.
I started with core18, but for later releases I switched to core20, however a suitable GNOME extension had not yet then been released, so I made an attempt to build up the required libraries myself – a big mistake! I did get some help from the developer community to make this work.
The best that can be said about this attempt was that it worked! However the size of the installable snap had doubled to something like 250 MB! Clearly, I had no idea what I was doing here, so I appealed to the community for some more advice, which I duly received.
The result is the following snapcraft.yaml, currently in use:
summary: A digital photography library manager and notebook
description: PhotoNoteBook is a digital photograph library manager enabling collections of images and the making of notes and memos for the collections and the photographs.
# this line enables LZO compression for the snap and speeds up first start time - hopefully!
plugs: [home, unity7, opengl, network, removable-media, optical-drive ,desktop, desktop-legacy, gsettings, x11, wayland]
stage-packages: [libgif7, liblcms2-2]
This version reduced the snap package to a reasonable size, but I still do not know if it’s at a complete minimum, or if there are still redundant elements that could be removed.
Conclusion & future
I am happy that my architecture choices have largely been sound, both in terms of the development environment and the package delivery approach. The codebase for the app was rebuilt for Windows 10 by using Intellij IDEA for Windows and the exact same source codebase. Anything that required specific coding for the underlying OS was built into the app and an OS detection routine was used to determine which code sections to use. It didn’t take too long to work out how to package the app for the Windows 10 Store (which requires a .msix package), but that is another story! The key message is that it is perfectly possible to create a client app for both Ubuntu Linux and Windows 10 using the same codebase built with JavaFX.
Interoperability between platform-specific versions was maintained – PhotoNoteBooks created on one version of the application can be moved to another version using the in-built Backup and Restore functionality and a suitable transfer media such as a network attached storage device, or USB storage.
For the record, currently PhotoNoteBook has had 600 downloads on Ubuntu (and around 20 flavours of Linux), and around 100 downloads on Windows 10. I think that qualifies as a “Proof of Concept” for using JavaFX for such cross-platform applications.
Finally, my concerns for the future of the Linux version are that the snap environment may develop in such a way that my approach will be invalidated and my App consigned to history. I have similar concerns for the Windows and Android versions. There is only so much time a retired software engineer operating as a single “kitchen table” developer can spend on re-engineering.
Huge thanks must be extended for the advice given by the Snapcraft community in helping me bring this snap to publication, and a huge thank you to Igor for giving me the opportunity to share my experience.
PhotoNoteBook can be downloaded free of charge with no in-app charging or other fees through the Snap Store, Microsoft Store, or the Google Play Store.
About Alan Watson:
Alan is now mostly retired. He started writing programs in BASIC using code sheets and punch cards while at technical college nearly 50 years ago! After a career as a sound engineer, he returned to software engineering by re-entering technical education with the UK’s Open University, which eventually led him to taking an MSc in computing. After a ten-year career at the real-time coalface programming in Assembler, Pascal, C and Delphi, Alan went into research. He then followed with a technical and business management career with a leading UK technology provider, and subsequently with a large UK service provider before finishing his full-time career with leading an Information Security Management department. He is now a part-time tutor in Information Security Management with the UK’s Open University, and holds MSc, Eur Ing, C.Eng, CITP, MBCS, MIET and CISSP qualifications.
We hope you enjoyed this story, and we’d like to feature more voices from the developer community on snapcraft.io. If you are a snap developer working on an interesting or unique project, feel free to reach out. You can email us: help at snapcraft.io, or start a thread in our forum.