I like baking bread. Well, most of all I love bread, and baking it myself is more convenient than tracking down a good bakery nearby. I used to bake a lot –and as a hipster I’ll tell you that it was before everyone else started doing it at the start of the pandemic– but I got bored of it some time ago and quit. But I’ve been eating more bread recently, and decided I could bake something better, and cheaper, than the bread from the grocery store. I’m pretty happy with what I’ve been baking too, they are all turning out with a good crust and soft insides. What’s the inside of the bread called? Anyway, my bread is good but a little bland, so I decided to also get back into sourdough bread to get some nice flavor.

Sourdough bread is made with sourdough starter, a live culture of yeast and bacteria that needs to be maintained with regular feedings of flour. So before you can bake sourdough bread, you must first prepare your starter and grow it for a few weeks to get its strength up, then keep it alive for as long as you want to keep baking. And before you can prepare a sourdough starter, you must first develop a mobile app.

Well, of course I could have just set up reminder in my calendar and skip having to develop an actual app. Or just use on of the proprietary ones. But why do the easy thing when you can waste tens –if not hundreds– of hours developing an app to maybe save a few seconds of your time or to make a point about using open source software? At least it will teach me some stuff along the way.

What tech to use?

But before I could start developing an app, I had to decide what to build it with. My first two requirements: it had to be cross platform, and it had to be able to show scheduled notifications on my phone. My first thought was to use a PWA. PWAs are web apps that can be “installed” on your phone like a regular app, but run entirely on your browser. It’s sort of an Electron-lite. That would be great since I already know a lot about web development, alas PWAs can’t send scheduled notifications while the app is in the background. That’s a pretty vital part of this project so that rules out PWAs. Also I think Safari has some problems with PWAs? So that also would have ruled it out if I somehow worked around the notifications.

Similar to PWAs, I could have used something like Electron to develop an app using web technologies. In this case it would be an actual app, so I could actually run code in the background and send scheduled notifications. Hurray! Except that I wasn’t sure I wanted to do this. My experience with apps developed this way is terrible, and it’s often very obvious someone basically put a web browser in kiosk mode. These apps are often slow, the UIs are completely devoid of the native look and feel, and they are often full of weird browserisms like being able to hold and select the text on the page. Maybe this perception is a form of Survivorship bias and there are lots of apps I used developed with these technologies that work great, but I decided to see what else was available first.

After more digging, I finally narrowed my choices down to 2 options: React Native and Flutter. React Native takes the usual React, and instead of rendering the virtual DOM into a real DOM it renders it to native UI components. React Native is something I used before, with my Bulgur Cloud project. While I liked React Native, I actually ended up rewriting Bulgur Cloud –in Next.js– so I was a little hesitant. Flutter on the other hand uses Dart, and is developed by Google with a focus on cross platform apps. Flutter works by rendering everything to a canvas… which sort of surprised me. My biggest concern with this was performance and accessibility, using native components gives you both out of the box. If you render everything yourself, then it’s your responsibility to handle accessibility and optimize rendering. Articles and discussions I came across online reassure me that Flutter is “doing better” at all of these, so hopefully it’s not a problem.

At this point I was still paralyzed by this decision, so I ended up creating a project with both to check out the out of the box example. Which really did not help, they both work and look fine. The code is pretty easy to understand with both. I have concerns with the future of both projects, because React Native feels too under control of Expo and Flutter feels too under control of Google.

I finally broke my paralysis by just going with React Native. I already know React, I know a ton of stuff about the React ecosystem, I’ve already used React Native before. Flutter and the Dart language look fine and I’m sure I could learn them, but it’s just easier for me to get this project off the ground if I use what I already know. I also remembered that the reason why I rewrote Bulgur Cloud was because React Native for Web is a bad experience, but in this case I don’t care about the web so it’s not a problem.

To Expo, or not to Expo?

When you start using React Native, the first thing you see is Expo. Look at the official getting started guide for React Native and it immediately recommends you use Expo. Expo then keeps coming up again and again in the docs. It’s easy to see why:

  • Expo solves a major getting started hurdle: installing Android Studio or XCode and connecting it to your phone so you can run it. Instead, you install the Expo Go app from your phones app store and scan a QR code to get started immediately.
  • Expo comes with a lot of built-in packages. If you go with Expo, you can use all these packages and you can still use all the regular React Native packages.
  • Expo doesn’t technically lock you into anything, you can “eject” from Expo at any time.

I decided to use Expo. While I have some concerns that Expo is an investor-backed company and not a community effort, the Expo packages are all open source. Expo Go is open source. And you can still install Android Studio and XCode and build everything yourself.

What else?

Expo comes with a lot out of the box, but there were some packages I wanted to play with and some packages I knew I wanted on top of that. Some of these include:

Tamagui

Tamagui is a UI kit for React Native. I first saw it advertised as a “headless” library meaning that it implements all the functionality but comes with no styles, so you style everything yourself. I later found out that this was not entirely correct though because Tamagui does come with default styles, but lets you opt out of them or customize them.

I really like Tamagui because it comes as a complete package. It’s not just UI components but also animations, CSS shorthands and a token based design system. It also comes with a cool “sub-theme” system that allows you to have variants of your themes, so you don’t just have a “dark” theme but you can also have “dark-forest” and “dark-blue” and “dark-whatever” to create different themes for different sections of you app without having to hand-code everything.

Expo-Sqlite

Expo-sqlite is an expo package, but it’s an optional one. It gives you Sqlite. That’s about all. Sqlite is awesome, so why not. There are some other options for storage, some even support additional features like encryption if that’s something you need. But I don’t think it’s crucial if I encrypt my sourdough starter data, so I’d rather take the Sqlite features.

nearform/sql

@nearform/sql is a library for SQL injection prevention. What’s cool about it is that you get to write SQL queries with the javascript string interpolation without risking SQL injection. So you can do:

DB.query(SQL`INSERT INTO students(name) VALUES (${name})`);

without little Bobby Tables ruining your day.

nearform/sql is not compatible with Expo-sqlite out of the box, but a basic wrapper function can easily get you there. Here it is, I’d publish it on npm but it’s so short you might as well just copy and paste it.

export function sql(strings: any, ...values: any[]): Query {
  const statement = SQL(strings, ...values);
  return {
    sql: statement.text,
    args: statement.values,
  };
}

SWR

SWR describes itself as “React Hooks for Data Fetching”. My little app is local only and doesn’t need to fetch data from anywhere, so why SWR? Well, SWR is great whenever you need to get data from anywhere outside of your apps own state. In my case, I need to get data out of SQLite and into my app, so I use SWR to fetch the data from the database.

The main use case for me is the caching and deduplication. SWR caches results and won’t fetch again if the same hook is called multiple times, so you can use your hooks everywhere without having to worry about the same data being fetched multiple times for no reason. You can also invalidate the cache whenever you want, which will invalidate all users of the hook and fetch the data just once to update everything.

You need some workarounds to get SWR working in React Native, but official docs have you covered.

Formik

I was hesitant and I dragged my feet the very first time I encountered Formik, but after trying it once I actually fell in love with it. It really does everything you need a form to do. The best part to me is that you get to avoid having a million useStates in your form, and thanks to the Formik context you can organize your code without drilling values and setters down into components.

You have to do a bit of work for Formik to work in React Native, but the official guide tells you what to do so it’s an easy change. Wrap that in a custom form input component and you can forget the incompatibility exists at all.

Some More Packages

This is getting really long (for my usual posts), so a few more quick mentions:

  • date-fns is the definitive date & time library. It’s incredible.
  • radash is like lodash. I’m honestly not fully sure if it’s better or worse, but I do keep coming back to it.
  • rrule makes calculating recurring dates easy. You can serialize and deserialize your rrule’s which I also appreciate.
  • ulidx is a random ID generator. It’s like nanoid, but at the cost of just a few more characters they are ordered by creation date.
  • zod is my favorite schema tool. Never leave home without it.

Fin

I’m about done with the basic functionality I wanted for this app, so I’m hoping to have a version of this up on the Google Play store by the end of the week.

Oh and I still haven’t made that sourdough starter,