[React Native] Android Widget Counter 생성 - 앱 -> 위젯 데이터 전달

mainsain·2023년 9월 20일

React Native

목록 보기
6/9
post-thumbnail

앱 → 위젯 데이터 전달

React Native 버튼 클릭 이벤트

주의

  • 아래 코드는 연습용이다.
  • React Native에서 로직이 돌아가면, 앱의 생명주기에 종속되는(앱을 종료했을 때) 문제가 발생할 수 있다.
  • 하지만, React Native에서의 동작이 Android Widget에 적용되는 과정을 연습하기 위해 아래 Flow를 작성했다.
const increaseBy1 = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				1,
			);
			setWidgetData(updatedNumber);
		}
	};

	const increaseBy10 = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				10,
			);
			setWidgetData(updatedNumber);
		}
	};

	const resetNumber = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				-widgetData,
			);
			setWidgetData(updatedNumber);
		}
	};
<Button title="increase by 1" onPress={() => increaseBy1()} />
<Button title="increase by 10" onPress={() => increaseBy10()} />
<Button title="reset" onPress={() => resetNumber()} />

React Native앱에서 버튼 3개를 만들었고, 각 버튼이 눌리면 StopWatch의 number의 값이 변경되도록 할 것이다.

Native Module에 이벤트 전달 기능 추가

@ReactMethod
public void updateNumber(int appWidgetId, int incrementValue, Promise promise) {
    try {
        SharedPreferences prefs = StopWatchModule.reactContext.getSharedPreferences("MyWidget", Context.MODE_PRIVATE);
        int currentNumber = prefs.getInt("number_" + appWidgetId, 0);
        int updatedNumber = currentNumber + incrementValue;

        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt("number_" + appWidgetId, updatedNumber).apply;

        promise.resolve(updatedNumber);

        WritableMap eventData = Arguments.createMap();
        eventData.putInt("updatedNumber", updatedNumber);
        eventData.putInt("appWidgetId", appWidgetId);

        StopWatchModule.reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit("onAppWidgetUpdate", eventData);
    } catch (Exception e) {
        promise.reject("UPDATE_NUMBER_ERROR", e);
    }
}

리엑트 메서드에서 updateNumber가 호출된다면, SharedPreferences에서 값을 꺼낸다음 변경해야한다.

editor로 updatedNumber을 기존 값에 더한 값을 업데이트해준다.

그리고 변경한 데이터를 위젯에 적용시켜야 하기 때문에 WritableMap 객체에 정보들을 담고, React Native에서 “onAppWidgetUpdate” 이벤트에 반응할 수 있도록 전달해준다.

문제 상황

RN에서 지금은 버튼을 눌러야 값이 업데이트된다. 하지만 이젠 버튼을 누르지 않아도 값이 업데이트 되게 처리를 해야한다.

똑같이 Widget에서도 값이 변경된다면 즉시 업데이트가 되어야 하지만, 지금은 그렇지 않다.

‘실시간 연동’을 위한 코드를 짜야한다.

RN 로직을 위젯에 연동 시키기

Native Module에 업데이트 로직 추가

Intent intent = new Intent(StopWatchModule.reactContext, StopWatch.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int[] ids = {appWidgetId};
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
StopWatchModule.reactContext.sendBroadcast(intent);

Intent 객체를 생성한 뒤, StopWatch 프로바이더에게 메시지를 보낼 것이다.

그 객체에 업데이트하라는 액션을 주고, 해당 Intent를 받는 리시버에게 메시징하기 위해 브로드캐스트해준다.

React Native에 값 업데이트

import React, { useEffect } from "react";
import { View, Text, Button } from "react-native";
import { NativeModules, DeviceEventEmitter } from "react-native";
import * as SecureStore from "expo-secure-store";

const { StopWatchModule } = NativeModules;

const WidgetText = () => {
	const [widgetData, setWidgetData] = React.useState<number>(0);
	const [appWidgetId, setAppWidgetId] = React.useState<String | null>(null);

	useEffect(() => {
		fetchStoredAppWidgetId();
		const listener = DeviceEventEmitter.addListener(
			"onAppWidgetUpdate",
			async (data) => {
				setAppWidgetId(data.appWidgetId);
				await SecureStore.setItemAsync(
					"appWidgetId",
					data.appWidgetId.toString(),
				);
			},
		);
		return () => {
			if (listener) {
				listener.remove();
			}
		};
	}, []);

	useEffect(() => {
		getWidgetData();
	}, [appWidgetId]);

	const fetchStoredAppWidgetId = async () => {
		const storedId = await SecureStore.getItemAsync("appWidgetId");
		if (storedId) {
			setAppWidgetId(storedId);
		}
	};

	const getWidgetData = async () => {
		if (appWidgetId) {
			const widgetData = await StopWatchModule.getNumber(Number(appWidgetId));
			setWidgetData(widgetData);
		}
	};
	
	const increaseBy1 = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				1,
			);
			setWidgetData(updatedNumber);
		}
	};

	const increaseBy10 = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				10,
			);
			setWidgetData(updatedNumber);
		}
	};

	const resetNumber = async () => {
		if (appWidgetId) {
			const updatedNumber = await StopWatchModule.updateNumber(
				Number(appWidgetId),
				-widgetData,
			);
			setWidgetData(updatedNumber);
		}
	};

	return (
		<View>
			<Text>{widgetData}</Text>
			<Button
				title="get widget data"
				onPress={() => getWidgetData()}
				disabled={!appWidgetId}
			/>
			<Text>{appWidgetId}</Text>
			<Text>Current state</Text>
			<Button title="increase by 1" onPress={() => increaseBy1()} />
			<Button title="increase by 10" onPress={() => increaseBy10()} />
			<Button title="reset" onPress={() => resetNumber()} />
		</View>
	);
};

export default WidgetText;

코드 전부인데, 사실 추가한건 딱히 없다. 코드 타입지정좀 바꿔주고 보기 편하게 조금 고친거? 버튼3개 추가한게 끝이다.

테스트

61을 누르고 앱을 켜보면,

id와 number가 정상적으로 나온다.

값을 변경해서 84로 만든 뒤, 홈 화면으로 돌아가면 위젯이 84로 업데이트 되어있는 걸 볼 수 있다.

Counter는 끝났고, 전체적인 흐름도 알았다. 이제,, 스톱 워치를 만들어보자.

profile
새로운 자극을 주세요.

0개의 댓글