React Native:为iOS和Android创建主屏幕小部件的最终指南
#javascript #reactnative #android #ios

小部件是很棒的工具,可以使您的主屏幕看起来更具吸引力,并为您提供快速和有用的信息。在这篇文章中,我们将向您展示如何为Android和iOS创建小部件,以及如何将它们整合到您的React Antive应用中。

小部件如何工作?

小部件可作为您应用程序的扩展。它不能用作独立应用程序本身。小部件有三种尺寸(小,中和大),并且可以是静态或可配置的。小部件在相互作用方面受到限制。它不能滚动,只能敲击。一个小的小部件只能具有一种类型的交互区域,而中件和大型小部件可以具有多个可敲击的相互作用区域。

为什么要开发一个小部件?

小部件通常是为了使用户通报重要信息并从主屏幕提供对其应用程序的访问,还可以将这些应用程序与竞争对手区分开来并保持用户参与度。

用反应天然的小部件

不幸的是,不可能使用React Antial创建主屏幕窗口小部件。但是不用担心,我们为您提供解决方案!在本指南中,我们将探讨如何使用本地小部件与您的React本地应用程序进行通信。让我开始!

tl; dr

如果您喜欢ð而不是ð,则可以在this repository中使用样品。欢迎拉动请求和其他类型的贡献!

ð•设置

1。创建一个新应用

react-native init RNWidget

2。添加一个依赖项,该依赖项将在窗口小部件和应用之间创建桥

yarn add react-native-shared-group-preferences

3。用于与本机模块进行通信添加此代码

import React, {useState} from 'react';
import {
  View,
  TextInput,
  StyleSheet,
  NativeModules,
  SafeAreaView,
  Text,
  Image,
  ScrollView,
  KeyboardAvoidingView,
  Platform,
  ToastAndroid,
} from 'react-native';
import SharedGroupPreferences from 'react-native-shared-group-preferences';
import AwesomeButton from 'react-native-really-awesome-button';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';

const group = 'group.streak';

const SharedStorage = NativeModules.SharedStorage;

const App = () => {
  const [text, setText] = useState('');
  const widgetData = {
    text,
  };

  const handleSubmit = async () => {
    try {
      // iOS
      await SharedGroupPreferences.setItem('widgetKey', widgetData, group);
    } catch (error) {
      console.log({error});
    }
    const value = `${text} days`;
    // Android
    SharedStorage.set(JSON.stringify({text: value}));
    ToastAndroid.show('Change value successfully!', ToastAndroid.SHORT);
  };

  return (
    <SafeAreaView style={styles.safeAreaContainer}>
      <KeyboardAwareScrollView
        enableOnAndroid
        extraScrollHeight={100}
        keyboardShouldPersistTaps="handled">
        <View style={styles.container}>
          <Text style={styles.heading}>Change Widget Value</Text>
          <View style={styles.bodyContainer}>
            <View style={styles.instructionContainer}>
              <View style={styles.thoughtContainer}>
                <Text style={styles.thoughtTitle}>
                  Enter the value that you want to display on your home widget
                </Text>
              </View>
              <View style={styles.thoughtPointer}></View>
              <Image
                source={require('./assets/bea.png')}
                style={styles.avatarImg}
              />
            </View>

            <TextInput
              style={styles.input}
              onChangeText={newText => setText(newText)}
              value={text}
              keyboardType="decimal-pad"
              placeholder="Enter the text to display..."
            />

            <AwesomeButton
              backgroundColor={'#33b8f6'}
              height={50}
              width={'100%'}
              backgroundDarker={'#eeefef'}
              backgroundShadow={'#f1f1f0'}
              style={styles.actionButton}
              onPress={handleSubmit}>
              Submit
            </AwesomeButton>
          </View>
        </View>
      </KeyboardAwareScrollView>
    </SafeAreaView>
  );
};

export default App;

const styles = StyleSheet.create({
  safeAreaContainer: {
    flex: 1,
    width: '100%',
    backgroundColor: '#fafaf3',
  },
  container: {
    flex: 1,
    width: '100%',
    padding: 12,
  },
  heading: {
    fontSize: 24,
    color: '#979995',
    textAlign: 'center',
  },
  input: {
    width: '100%',
    // fontSize: 20,
    minHeight: 50,
    borderWidth: 1,
    borderColor: '#c6c6c6',
    borderRadius: 8,
    padding: 12,
  },
  bodyContainer: {
    flex: 1,
    margin: 18,
  },
  instructionContainer: {
    margin: 25,
    paddingHorizontal: 20,
    paddingTop: 30,
    borderWidth: 1,
    borderRadius: 12,
    backgroundColor: '#ecedeb',
    borderColor: '#bebfbd',
    marginBottom: 35,
  },
  avatarImg: {
    height: 180,
    width: 180,
    resizeMode: 'contain',
    alignSelf: 'flex-end',
  },
  thoughtContainer: {
    minHeight: 50,
    borderRadius: 12,
    borderWidth: 1,
    padding: 12,
    backgroundColor: '#ffffff',
    borderColor: '#c6c6c6',
  },
  thoughtPointer: {
    width: 0,
    height: 0,
    borderStyle: 'solid',
    overflow: 'hidden',
    borderTopWidth: 12,
    borderRightWidth: 10,
    borderBottomWidth: 0,
    borderLeftWidth: 10,
    borderTopColor: 'blue',
    borderRightColor: 'transparent',
    borderBottomColor: 'transparent',
    borderLeftColor: 'transparent',
    marginTop: -1,
    marginLeft: '50%',
  },
  thoughtTitle: {
    fontSize: 14,
  },
  actionButton: {
    marginTop: 40,
  },
});

让我解释一下如何在应用程序中使用共享grouppreferences和SharedStorage。从库中导入共享Grouppreferences,您可以使用键,值和组将项目存储在stetItem方法中。在此示例中,密钥将是widgetkey,该值将是widgetdata,一个包含用户输入的JavaScript对象,该组将是该组的名称,该名称将在应用程序和widget之间共享信息。当我们获得Swift Code时,我们将对此进行更多的讨论。

现在,对于Android,我们将使用共享storage。您不需要为此安装任何其他库,因为它包含在React Native软件包中。该值将是一个序列化的JavaScript对象,该对象将转换为字符串并使用SET共享storage方法保存。容易的peasy,对吗?

对于本机代码。让我们从iOS开始。

iOS

实现

1。在Xcode中打开您的应用程序项目,然后选择“文件”> new> target。

Image description

2。

Image description

3。输入扩展名称。

Image description

4。如果小部件提供了可用户可容纳的属性,请检查Incluble配置意图复选框。

Image description

5。单击完成。

6。如果它要求激活该方案,请单击激活。

Image description

7。您的小部件已准备就绪!现在,您有一个新文件夹,其中包含窗口小部件的所有基本文件。

Image description

8。在我们继续下一步之前,请测试您的基本小部件。为此,只需打开您的终端并使用以下命令运行应用

react-native run-ios

9。要添加一个小部件,您必须点击并握住主屏幕,直到出现在左上角。当您单击图标时,将显示一个应用程序列表。我们新创建的应用程序应包括在内。

Image description

10。要与React Native小部件进行通信,我们必须添加App group

Image description

11。

import WidgetKit
import SwiftUI
import Intents

struct WidgetData: Decodable {
   var text: String
}

struct Provider: IntentTimelineProvider {
   func placeholder(in context: Context) -> SimpleEntry {
      SimpleEntry(date: Date(), configuration: ConfigurationIntent(), text: "Placeholder")
  }

  func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
      let entry = SimpleEntry(date: Date(), configuration: configuration, text: "Data goes here")
      completion(entry)
  }

   func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
      let userDefaults = UserDefaults.init(suiteName: "group.streak")
      if userDefaults != nil {
        let entryDate = Date()
        if let savedData = userDefaults!.value(forKey: "widgetKey") as? String {
            let decoder = JSONDecoder()
            let data = savedData.data(using: .utf8)
            if let parsedData = try? decoder.decode(WidgetData.self, from: data!) {
                let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: entryDate)!
                let entry = SimpleEntry(date: nextRefresh, configuration: configuration, text: parsedData.text)
                let timeline = Timeline(entries: [entry], policy: .atEnd)
                completion(timeline)
            } else {
                print("Could not parse data")
            }
        } else {
            let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: entryDate)!
            let entry = SimpleEntry(date: nextRefresh, configuration: configuration, text: "No data set")
            let timeline = Timeline(entries: [entry], policy: .atEnd)
            completion(timeline)
        }
      }
  }
}

struct SimpleEntry: TimelineEntry {
   let date: Date
      let configuration: ConfigurationIntent
      let text: String
}

struct StreakWidgetEntryView : View {
  var entry: Provider.Entry

  var body: some View {
    HStack {
      VStack(alignment: .leading, spacing: 0) {
        HStack(alignment: .center) {
          Image("streak")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 37, height: 37)
          Text(entry.text)
            .foregroundColor(Color(red: 1.00, green: 0.59, blue: 0.00))
            .font(Font.system(size: 21, weight: .bold, design: .rounded))
            .padding(.leading, -8.0)
        }
        .padding(.top, 10.0)
        .frame(maxWidth: .infinity)
        Text("Way to go!")
          .foregroundColor(Color(red: 0.69, green: 0.69, blue: 0.69))
          .font(Font.system(size: 14))
          .frame(maxWidth: .infinity)
        Image("duo")
          .renderingMode(.original)
          .resizable()
          .aspectRatio(contentMode: .fit)
          .frame(maxWidth: .infinity)

      }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
  }
}

@main
struct StreakWidget: Widget {
  let kind: String = "StreakWidget"

  var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
      StreakWidgetEntryView(entry: entry)
    }
    .configurationDisplayName("My Widget")
    .description("This is an example widget.")
  }
}

struct StreakWidget_Previews: PreviewProvider {
  static var previews: some View {
    StreakWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), text: "Widget preview"))
      .previewContext(WidgetPreviewContext(family: .systemSmall))
  }
}

基本上:

  • 从我们创建之前创建的共享组中读取userDefaults对象
let userDefaults = UserDefaults.init(suiteName: "group.streak")
  • 获取数据(已以字符串形式编码)
let savedData = userDefaults!.value(forKey: "widgetKey")
  • 解码为对象
let parsedData = try? decoder.decode(WidgetData.self, from: data!)
  • 创建上述对象的时间表
let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: entryDate)!

请注意,添加到时间表结构的对象必须遵守时间表协议,这意味着他们需要具有日期字段,而无需其他。这是要记住的重要信息。

这就是iOS的全部。只需运行NPM启动并在虚拟设备或真实设备上测试您的应用。

安装了该应用程序后,您需要做的就是从小部件列表中选择小部件并将其放在主屏幕上。

Image description

接下来,打开应用程序并在输入字段中键入某些内容,按Enter,然后返回主屏幕。

Image description

现在是iOS,让我们探索如何在Android上完成同一件事。

ðüstroid

实施

1。在Android Studio上打开Android文件夹。然后,在Android Studio内部,右键单击res> new> widget> app widget:

Image description

2。名称并配置小部件,然后单击完成:

Image description

3。现在运行该应用程序,您可以看到可用的小部件。

4。要在窗口小部件和React Antive应用之间进行通信,我们将使用共享Preferences Android Android Antial Module,就像iOS的UserDefaults一样。

这涉及将新的SharedStorage.javaSharedStoragePackager.java文件添加到与您的mainapplication.java的目录。

sharedstorage.java

package com.rnwidget;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;

public class SharedStorage extends ReactContextBaseJavaModule {
 ReactApplicationContext context;

 public SharedStorage(ReactApplicationContext reactContext) {
  super(reactContext);
  context = reactContext;
 }

 @Override
 public String getName() {
  return "SharedStorage";
 }

 @ReactMethod
 public void set(String message) {
  SharedPreferences.Editor editor = context.getSharedPreferences("DATA", Context.MODE_PRIVATE).edit();
  editor.putString("appData", message);
  editor.commit();

  Intent intent = new Intent(getCurrentActivity().getApplicationContext(), StreakWidget.class);
  intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
  int[] ids = AppWidgetManager.getInstance(getCurrentActivity().getApplicationContext()).getAppWidgetIds(new ComponentName(getCurrentActivity().getApplicationContext(), StreakWidget.class));
  intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
  getCurrentActivity().getApplicationContext().sendBroadcast(intent);

 }
}

sharedStoragepackager.java

package com.rnwidget;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SharedStoragePackager implements ReactPackage {

 @Override
 public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
  return Collections.emptyList();
 }

 @Override
 public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
  List<NativeModule> modules = new ArrayList<>();

  modules.add(new SharedStorage(reactContext));

  return modules;
 }

}

5。更改您的应用程序的包装名称,如AndroidManifest.xml中所示的Android> App> app> src> main。

Image description

6。

packages.add(new SharedStoragePackager());

7。在设置桥梁后,让我们继续以streakwidget.java接收数据。为了更新小部件的内容,我们需要使用共享限额并使用UpdateAppwidget方法进行管理。在这里,用以下方式替换您现有的代码。

package com.rnwidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.content.SharedPreferences;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Implementation of App Widget functionality.
 */
public class StreakWidget extends AppWidgetProvider {

    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);
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.streak_widget);
            views.setTextViewText(R.id.appwidget_text, appData.getString("text"));
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}

8。现在,让我们谈谈小部件的外观。此步骤是可选的,但是我们将使用与iOS示例中相同的设计。在Android Studio中,导航到您的应用程序> Res> Res> streak_widget.xml文件。您可以像这样的设计预览

查看设计预览

Image description

9。就是一切!在Android设备上进行测试

Image description

结论

出色的工作!通过学习如何使用React Antial创建AppWidget,您可以为工具包添加了一项宝贵的技能。即使这个主题对您来说是新的,也不担心您的应用程序中易于使用且易于使用。保持良好的工作!

请给它一个拍手ðð»并与您的朋友分享!

请留下您的反馈意见。

如果您想支持我作为作家,请考虑在Dev上关注我,并在LinkedIn上与我联系。