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 · 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.

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:

Select Today Extension, and click Next:

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

If Xcode asks to activate the scheme, click Activate.

You should see the Widget folder on your project:

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

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):

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.

You should see the reference in the code like this:

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:

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:

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.

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

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:

Select Cocoa Touch Class, and click Next:

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:

Now you should see the new files on your project:

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:

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):

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:

Name and configure your widget, and click Finish:

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:

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.

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:

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:

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:

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:

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:

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 MainActivity.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:

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):

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

Code: Check the entire code here.

I hope this tutorial helped you.

Better Programming

Advice for programmers.

Thanks to Zack Shapiro

Andre Pimenta

Written by

CTO, Full Stack Developer, Entrepreneur. linkedin.com/in/andrepimenta7

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade