React Native: How To Build a Home Screen Widget for iOS and Android

Spoiler alert: React Native can’t be used on the widget itself.

Andre Pimenta
Nov 17, 2019 · 10 min read

Objective: Learn how to build a native widget and share information with your React Native app.

TL;DR: Check the entire code and follow the commits here.

Advanced feature: It’s recommended you know React Native well and have some experience with Android/iOS development.

Image for post
Image for post
How to build a home screen Widget for iOS and Android using React Native

The widget is an important addition to an app and often a highly requested feature. Unfortunately, you can’t use React Native directly to build the widget. Why? Because iOS has a 16 MB memory limit for app extensions, and React Native would take most of it from the start. Trust me, I tried.

So the alternative is to build each widget natively with Java on Android and Swift on iOS. And normally, you need the widget to be able to share information with the main app — in this case, with the React Native app. This tutorial will show you how this can be achieved.

I named the project ReactNativeCreateWidgetTutorial, and it will look something like this:

iOS

1. Create the widget files

Create the widget (on iOS, it’s called the Today Extension) by opening your workspace file on Xcode, and click on File > New > Target:

Image for post
Image for post

Select Today Extension, and click Next:

Image for post
Image for post

Give it a name, and choose your preferred language. In this case, I’m going to choose Swift. Click Finish:

Image for post
Image for post

If Xcode asks to activate the scheme, click Activate.

You should see the Widget folder on your project:

Image for post
Image for post

Now you should see the widget on your widget screen, and you can already activate it:

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Now, let’s checkout the structure of the widget. Go to your Widget folder, and click on the storyboard file. Click on the assistant-editor button (the one with two circles on the top right of the screen):

Image for post
Image for post

You can see there are two functions called viewDidLoad and widgetPerformUpdate. The function viewDidLoad runs every time the user switches to the widgets screen. So this is the place where you should initialize variables, labels, or views. The function widgetPerformUpdate is called whenever you need to update the widget contents.

2. Customize the widget UI

You can also see there’s a label with the text “Hello World.” Let’s customize that label by dragging it into our code. Right click on the label, and drag the New Referencing Outlet directly to the code inside the class.

Image for post
Image for post

You should see the reference in the code like this:

Image for post
Image for post

Let’s modify the text of the label inside the viewDidLoad function:

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view from its nib.
//ADD THIS LINE
textLabel.text = "My first Widget"
}

If you run the app, you should see the label was initialized correctly:

Image for post
Image for post

Tip: You can run the widget without running the entire app by going to Xcode and selecting the widget as the target and then clicking run:

Image for post
Image for post

3. Create the communication channel between the widget and the React Native app

OK, now the fun part. Let’s have our React Native app control what the widget shows. To do this, we have to implement a way for the React Native app to communicate with the widget. We’re going to do this via a shared storage between the widget and the React Native app. This can be accomplished using the UserDefaults iOS native module.

We will make the React Native app write to UserDefaults and have the widget read from it. The first problem we find is there is no official React Native way to interact with UserDefaults, and I didn’t found any good library to do this. So let’s implement this ourselves by creating a bridge between React Native and native iOS.

First, we’ll create a shared space inside our app that’ll allow communication between the widget and the app. This can be done with App Groups, which is located under the Capabilities tab.

Image for post
Image for post

Let’s activate it and then select a group or add one if it’s empty:

Image for post
Image for post

OK, now that we have the App Groups enabled, let’s implement a way for React Native to write to the UserDefaults by creating a native bridge.

Select your project, and right click to add a new file:

Image for post
Image for post

Select Cocoa Touch Class, and click Next:

Image for post
Image for post

Because this is a storage that’ll be shared by the widget and the React Native app, let’s call it SharedStorage. Select Objective-C, and click next:

Image for post
Image for post

Now you should see the new files on your project:

Image for post
Image for post

Let’s edit those files. First copy this into the SharedStorage.h file:

And this into the SharedStorage.m file:

Important: Change the group name (group.com.createwidget.pimenta) to the one you created on App Groups.

Now, you’re able to call SharedStorage on React Native. As you can see on the code, all it does is receive a JSON and store it on the UserDefaults storage.

4. Control the widget content with the React Native app

On the React Native side, let’s import the module:

import { NativeModules } from 'react-native';const SharedStorage = NativeModules.SharedStorage;

And then let’s send some data to the storage:

SharedStorage.set(
JSON.stringify({text: 'This is data from the React Native app'})
);

You can do this, for example, on your App.js or wherever you find it appropriate to set the data on your React Native code:

Image for post
Image for post

Now, all that’s left is to have the widget read the data and insert it into the UI. We’re going to connect the widget to the UserDefaults, read its data, and then print the data on the “Hello World” textLabel.

Go to your TodayViewController.swift file in the Widget folder, and edit the viewDidLoad function, like this:

//CHANGE THE GROUP NAME
let userDefaults = UserDefaults(suiteName:"group.com.createwidget.pimenta")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view from its nib.
//ADD THIS
do{
let shared = userDefaults?.string(forKey: "data")
if(shared != nil){
let data = try JSONDecoder().decode(Shared.self, from: shared!.data(using: .utf8)!)
textLabel.text = data.text
}
}catch{
print(error)
}
}

You can check the whole file here.

Important: Change the group name (group.com.createwidget.pimenta) to the one you created on App Groups.

Let’s run the application, and check the widget (note that you have to run and open the app in order to let it write the SharedStorage):

Image for post
Image for post

And that’s it for iOS — now to the Android part.

Android

1. Create the widget files

Open the Android folder on Android Studio. Then, inside Android Studio, right click on res > New > Widget > App Widget:

Image for post
Image for post

Name and configure your widget, and click Finish:

Image for post
Image for post

The next window will show the several files that are going to be added to the project. The Widget.java file is where we’ll code the widget behavior. The rest of the files are where we’ll implement the widget UI components. Click Add:

Image for post
Image for post

Now if you run the app, you should see the widget available:

Note: If you did the iOS tutorial first, comment the lines where you called SharedStorage on your React Native code as it’s not yet implemented on Android, and it’s going to cause an error.

Image for post
Image for post
Image for post
Image for post

OK, now that we have the widget running, let’s customize it a little bit.

2. Customize the widget UI

On Android Studio, open your app folder and select the file res -> layout -> widget.xml:

Image for post
Image for post

This opens the layout of your widget. As you can see, there’s a text view with the text “EXAMPLE.” You can see more details if you click on it:

Image for post
Image for post

Let’s change the text of the label. As you can see on the label properties, the text is referring to “@string/appwidget_text.” This is located at res > values > strings.xml. Open this file, and you’ll see the text defined:

Image for post
Image for post

If you change the text from “EXAMPLE” to “HELLO,” save the file, and run the app again. It’ll be reflected on the widget. Let’s do it:

Image for post
Image for post

OK, now we know how to customize the widget. Next, let’s implement a way to do this via the React Native app.

3. Create the communication channel between the widget and the React Native app

OK, now the fun part again. Let’s have our React Native app control what the widget shows. To do this, we have to implement a way for the React Native app to communicate with the widget. We’re going to do this via a shared storage between the widget and the React Native app. This can be accomplished using the SharedPreferences Android-native module.

We’ll make the React Native app write to the SharedPreferences and have the widget read from it. The first problem we find is there is no official React Native way to interact with SharedPreferences, and I didn’t find any good library to do this. So let’s implement this ourselves.

In order to call SharedPreferences, we’ll create a bridge between React Native and native Android.

Add two files on your project next to MainActivity.java called SharedStorage.java and SharedStoragePackager.java:

Image for post
Image for post

Copy the following into your SharedStoragePackager.java file:

And this into your SharedStorage.java file:

Important: Change the package name com.reactnativecreatewidgettutorial to your own. And also the Widget.class to the name of your widget class.

Now you are able to call SharedStorage on React Native. As you can see on the code, all it does is receive a JSON and store it on the SharedPreferences storage and then tell the widget to update itself.

In order for Android to know your module exists, add it to your packages list in your MainApplication.java file.

new SharedStoragePackager()

4. Control the widget content with the React Native app

On the React Native side, let’s import the module.

Note: If you already modified the React Native code, skip this part.

import { NativeModules } from 'react-native';const SharedStorage = NativeModules.SharedStorage;

And then let’s send some data to the storage:

SharedStorage.set(
JSON.stringify({text: 'This is data from the React Native app'})
);

You can do this, for example, on your App.js or wherever you find it appropriate to set the data on your React Native code:

Image for post
Image for post

Now all that’s left is to have the widget read the data and insert it on the UI. We’re going to connect the widget to the SharedPreferences, read its data and then print the data on the “HELLO” label.

Go to your Widget.java file in the Widget folder, and import the following modules:

import android.content.SharedPreferences;
import org.json.JSONException;
import org.json.JSONObject;

Now modify the updateAppWidget function, like this:

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {

try {
SharedPreferences sharedPref = context.getSharedPreferences("DATA", Context.MODE_PRIVATE);
String appString = sharedPref.getString("appData", "{\"text\":'no data'}"); JSONObject appData = new JSONObject(appString); // Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
views.setTextViewText(R.id.appwidget_text, appData.getString("text"));
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}catch (JSONException e) {
e.printStackTrace();
}
}

You can check the whole file here.

We are modifying the function updateAppWidget, which is responsible for updating the widget content, to make the widget read from the SharedPreferences db and print the data on the text label.

That’s it — now the React Native app controls the widget content. Let’s run the application, and check the widget (note that you have to run and open the app in order to let it write to the SharedStorage):

Image for post
Image for post

And that’s it for coding. Now you have the foundation where you can build upon the rest of the widget. Let’s check the final results.

Results

Image for post
Image for post

Code: Check the entire code here.

I hope this tutorial helped you.

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store