A major feature of Outboard, my iOS app written with ClojureScript on React Native, is the ability to add places to your lists from a share extension inside Apple’s Maps, Google Maps, Yelp, or other apps that supply URLs. React Native ships with AsyncStorage, a LocalStorage analog for iOS and Android apps, but on iOS, that data is sandboxed to the app alone. To share between an app and an extension, you need to use App Groups, and roll your own persistence layer to interact with it. In this article, I’ll explain how to do that. You should first have an App Group set up on the developer portal and have an entitlement to it added both your app and its extension; you can follow this guide to get to that point.
The native DataManager
First we’ll implement the native persistence layer that will write keys to
UserDefaults object for our app group; I’ll do that in Swift. It’s a
simple class with two functions, one to read and one to write.
@objc macro calls here; we need to eventually provide an ObjC bridge
for ReactNative to talk to it. Our
loadData function takes a key and a
callback function; that function is what we’ll provide in ClojureScript later
to handle reads. The
saveData function simply writes without any callback. We
haven’t done anything with errors in either function; you should build that in
to handle your specific use case. The aforementioned ObjC bridge goes in
The ClojureScript layer
The bare code for saving/loading is also two simple functions, mediated by
A few things to note here:
- You may need to provide externs, or use something like cljs-oops for the
.-DataManagercall under advanced compilation
- I’m using
read-stringto ednify the data I’m saving
- In real life, I actually use
get-itemto avoid callback hell, but I’m providing the minimum viable implementation here
At this point, you should be able to put items into storage from the REPL and retrieve them.
Connecting to Re-Frame
The real power from this approach comes at the final Re-Frame step, where I
UserDefaults on every change automatically:
You’ll need to make sure that you initialize your state from
(get-item :saved-app-state (fn [_ val] (dispatch-sync [:initialize-db val]))
in your app startup as well.
Now, every time I call
(dispatch [:add-something :something]), that change
app-db will also immediately be persisted locally; any changes made
from the share extension will reflect in the app and vice-versa.
Setting up app groups and persisting to
UserDefaults can be a fiddly
experience in XCode, but the actual code to do so is straighforward. With
to handle automatically persisting your data in the background for you, you can
treat your Clojurescript-written app and extensions as writing and reading from