I am building a cross-platform application for rat breeders to use in order to keep records of their rats and litters, called Basemix (at least for now). I have finally published it on Google Play and the Microsoft store for early access, along with a macOS build distributed through… Google drive for now.
It’s the start of a journey, I have a long list of features I plan to add, a lot of feedback to address already, some pretty technical problems I still want to solve and that’s before we even get to trying to make it look marginally better than “out of the box bootstrap app”. But it’s here, I’m having fun working on it, and I’m hoping it’ll start to provide real value to people soon.
I’m far from calling it “Released” or “Stable” right now, and tentatively reserving the right to push breaking changes, but so far I haven’t had to do that and have managed to push a handful of updates to a handful of users quite happily.
Why rats though???
Rats are lovely pets, which my wife Morgan introduced me to some years ago. Since then and over time we’ve been engaging with the UK rat community, learning more, increasing our standards of care and have finally moved into breeding and showing rats ourselves. She’s absolutely the brains and brawn of the operation though, I must add.
While I won’t go into detail in this post, I will insist that breeding ethically and for the right reasons is important. None of this is a commercial venture, and shouldn’t be. You can read more on our rattery website if you’re interested.
Tech talk
Let’s talk about how I’m building this and what I’m using. Special shout out to JetBrains Rider which has been, ironically, much easier to work with than Visual Studio for MAUI development despite the relative immaturity of MAUI.
Framework: MAUI (dotnet 7)
Microsoft’s Multi-platform-except-lets-pretend-linux-doesn’t-exist-App-UI is the primary force behind Basemix. I tried and looked into a few cross-platform and mobile-friendly frameworks and for varying reasons (speed of development due to my familiarity with dotnet and getting feedback not a small one) eventually committed to MAUI for the sake of moving forwards with something “guaranteed” to work with everything I needed, and because it felt good enough in spite of a few quirks. MAUI’s two primary flavours are XAML or “Blazor Hybrid”, which embeds a webview in a window and leans on Blazor for the UI. I’m using the Blazor hybrid flavour so that I can lean into my (miniscule) HTML and CSS skills (and because the XAML controls seemed in a really bad state on mac when I tried them, which is probably more important).
I was quite surprised to find out that a file saving dialogue did not exist as a part of the MAUI framework and I’ve had to lean on the MAUI CommunityToolkit project for that of my application (I let people easily make database backups and generate PDF files, for example). I really appreciate that little toolkit for this.
Persistence: SQLite
I’m using SQLite to store records, because it is an extremely wide and well supported format with a great deal of mature tooling and fully suits my “You Own Your Data” fully-offline model for Basemix. It works on all platforms and is flexible and evolvable over time too. Plus I like using it!
To handle database changes over time I’m using DbUp to track and run SQL scripts on startup, allowing me to evolve the schema gradually. The flexibility and reliability of it is fantastic, and because I’m dealing with offline database files, having the journal in-database is very useful for diagnosing issues.
To interact with SQLite I’m using Dapper and Microsoft.Data.Sqlite, a very low-ceremony way to work directly in SQL and map to simple classes/records in my domain. This makes working with everything really easy and predictable.
PDF generation: QuestPDF
Basemix gives users the ability to generate a pedigree (a family tree of direct ancestors only) for any rat. These pedigrees are used for various reasons but a common one is for passing on ancestor data to new pet homes, which means I needed to produce something more portable than an in-app rendering. I picked up QuestPDF to do this which has a really generous license (it will always be free for me) and a really pleasant API which was very quick to get up and running with, and well documented opinions about how to generate complex layouts.
To keep things portable, predictable and easily testable I’m also embedding fonts from Google Fonts specifically for PDF generation.
Data importing/exporting: ExcelDataReader
To assist users migrate onto Basemix from another system, and to lean into the “You Own Your Data” philisophy I’m promoting, I’m building facilities to import rat data from various other popular applications/formats and I plan to allow an export to an Excel file which should be easier than SQLite for most users to work with. While I really like the simple and foolproof style of ExcelMapper, one of the Excel files I need to read from proved to be quite tricky; I put significant effort into more manual reading/mapping/parsing to accommodate it. I switched to using ExcelDataReader which is a lot more manual legwork to use but also offered me the control I needed to make progress here.
Testing
I’m not going to bleat too hard about what I used here as I didn’t really sit down and spend ages thinking about my options. Actually writing tests was more important. I used xunit pretty much by default (as it’s what I’ve been using at work for over two years, so, its fresh in the mind. Pros and cons, all), Shouldly for assertions and Bogus to help with data generation.
I’ve got tests targeting my domain logic, my interactions with SQLite (creating a SQLite file in the tests and using it), parsing from Excel files and, though a bit shoddy in execution, directly targeting the logic inside of my razor components. The biggest gap is around the PDF generation, I’m uncertain what to do there and if it’s worth my time investing in currently with the ease of manually testing that today.
Distribution
I had two main things I wanted to satisfy when distributing Basemix:
- Ease of installation (If people have to read a guide, I’ve gone wrong)
- Cost of distribution (I give this app away for free!)
Distributing via the primary app store on every platform seemed the best way to go. This gets my code signed (which is important for installation ease - users devices won’t insist that it’s an unrecognised developer, and on some platforms this makes applications really hard to run), gets my application hosted externally, gives me and users a nice mechanism to update the application and there is an app store embedded in every platform I target today.
The downsides here are mainly:
- Cost of distribution for apple devices. Microsoft store and Google Play both let you do your thing with a one-time fee (and a cut of app revenue) which is around the £20 mark, extremely justifiable. Apple store participation would cost me £79 per year. I’m willing to spend that in the long term, but not before the app is off the ground, hence my “Google Drive/Unrecognised Developer” distribution method for macOS and no iPhone version today.
- Longevity. Being on an app store requires proactive participation from me - some app stores are notorious for deprecating applications that receive no updates for a period of time and if they do that, there’s often no alternative installation. I don’t see myself stopping anytime soon but if I get struck by a comet, Basemix’s days are artificially numbered.
I’m considering opening the source at some point too as at the very least that doesn’t lock the application away forever when the comet strikes me down. There’s still a technical barrier to entry if I do, but at least there’s the possibility of someone else picking it up.
Parting Gift 🐀
Thanks for reading what is essentially a giant advert for a bunch of tools and libraries. If you’re thinking of building something similar (fully offline cross platform database-dependent app) or you have more questions about Basemix or the tech I’m using, I’d be happy for you to get in touch to talk about it, you can find my social links on the homepage.