Intro
Recently I noticed that people are starting to use one of my side projects and I decided to dedicate a bit more time to it. Naturally the idea to add more platforms so people can use it outside of desktop chrome came to my mind. I think I spent more time planning how to do it as simple and easy as possible so it may be worth it to share the steps with the world.
Prerequisites
Chrome extension (kinda obvious from the title)
JS based mobile framework of your choosing. I picked Apache Cordova over React Native simply because I have more experience with it and I am constantly delaying tinkering with React Native
Steps:
1 - Code sharing
The main idea is that the code base in my case is almost exactly the same. You can either split the code into internally used libraries and bundle different options (Chrome vs Android) depending on the device the code is being ran on, or handle the differences internally in simple abstractions/wrappers inside of the modules that depend on the device/OS to guarantee an interface to be used from the app, regardless of the underlying implementation and platform.
In both cases you will have to pick a way to actually share the code afterwards. There are many ways to do that:
- Move the chrome extension to the www/js folder of the app (super crude... please don't do it!)
- Use yarn workspaces to define both projects and the core functionality and import it as needed
- Use lerna to the same as option 2 Here is a comparison between yarn workspaces and lerna -> link
My vote is for option 2 because it is flexible without being an overkill, since most of the functionalities of Lerna are not needed in this case. It is also a lot easier to set up and faster to run.
Because the differences between Chrome and Android, in my case, are only limited to the storage, notifications and opening new tab for authentication purposes, I've chosen to make simple wrappers for the interfaces Chrome and Android provide and mask the differences there.
2 - Handling differences in the code or adding abstractions
Lets start by adding a utility function to detect what to use:
const inExtension = () => chrome && chrome.storage && chrome.storage.sync;
There are probably better ways to detect this. For instance you can check all chrome libraries that you are using before deciding or just set a global property for the platform.
For the storage it is a pretty straightforward approach. For the extension I am using chrome.storage.sync because it automatically syncs data across all user devices and for the android version I am using localStorage, because it provides all needed for a POC and has a pretty close interface to chrome.storage.sync. For example, the wrapper looks something like this:
const storage = {
set: (obj, callback) => {
if (inExtension()) {
chrome.storage.sync.set(obj, callback);
} else {
const keys = Object.keys(obj)
keys.forEach(key => {
localStorage.setItem(key, obj[key])
})
callback && callback()
}
},
clear: callback => {
if (inExtension()) {
chrome.storage.sync.clear(callback)
} else {
localStorage.clear()
callback && callback()
}
},
...
}
localStorage is synchronous so you can move the callbacks just after using the storage.
The same is valid for handling communication between the background page and the popup that opens when you click on the extension icon.
For the notifications this would depend on what exactly you want to do and how you are using the notifications. Because of that you may opt for a different notifications library for Cordova or React Native, etc.
3 - Handling differences in the UI
You can do it in a variety of different ways, mostly depending on what is your preferred styling library:
- You can use the inExtension function we declared earlier to change the styling to a Styled Components or something similar, component.
- You can simply add a CSS class to the top most wrapper or the body, or individually to every component.
- Probably a few other ways to do it, since the JS world is pretty un-restricted.
It is entirely up to the tech stack you are most comfortable with. Since the differences I have are just the order of the elements inside the app and whether or not the volumebar is shown (I forgot to mention that its an audio player app) I opted to just add an additional class to the main wrapper and then simply add the following styling for it
flex-direction: column-reverse;
The volumebar is handled in the component, because I prefer to not render things when they aren't needed, instead of hiding them from the CSS, so when we are not inside an extension, the component doesn't get rendered.
There is no step 4. This is all of it. It is so easy because we can use the same language for both platforms and I don't do anything complicated with either, so I can get away with some simple wrappers.
I am aware that in the future the two versions will start to differentiate more and more when with every platform specific feature. However if you want to make something simple and see how your users will react to it, this should be more than enough.
If you want to try out the extension or android app that gave me the idea of the article, you can find them here -> Chrome Extension and Android App. Both versions are completely free, I am using them to try out things like this article and because people seem to use and like them.
See you all soon :)
Top comments (0)