ساخت ویجت با React Native برای iOS و اندروید — از صفر تا صد
در این مقاله با شیوه ساخت یک ویجت صفحه اصلی با React Native و اشتراک اطلاعات با اپلیکیشن ریاکت نیتیو آشنا میشویم. گرچه برای مطالعه این راهنما نیاز به پیشزمینه خاصی وجود ندارد، اما داشتن مقداری تجربه در زمینه توسعه اندروید و iOS به درک بهتر مطالب کمک خواهد کرد.
افزودن یک ویجت به اپلیکیشن حائز اهمیت بالایی است و عموماً تقاضای بالایی برای این قابلیت وجود دارد. متأسفانه امکان ساخت ویجت با استفاده مستقیم از ریاکت نیتیو وجود ندارد. دلیل این امر آن است که iOS محدودیت 16 مگابایت حافظه برای اکستنشنهای اپلیکیشن دارد و ریاکت نیتیو اغلب این مقدار را از همان آغاز مصرف میکند.
بنابراین به طور جایگزین باید هر ویجت را به صورت نیتیو با جاوا روی اندروید یا با سوئیفت روی iOS بسازیم. به طور معمول ویجت باید بتواند اطلاعت را با اپلیکیشن به اشتراک بگذارد که در این مورد یک اپلیکیشن ریاکت نیتیو خواهد بود. در این راهنما شیوه اجرای این وظیفه را نیز توضیح خواهیم داد.
ما نام پروژه خود را ReactNativeCreateWidgetTutorial گذاشتهایم و ظاهر آن روی هر دو پلتفرم مانند زیر است:
iOS
ابتدا بخش iOS را توضیح میدهیم.
ایجاد فایلهای ویجت
با باز کردن فایل workspace روی Xcode و رفتن به منوی File > New > Target ویجت را ایجاد میکنیم.
توجه داشته باشید که روی iOS به نام Today Extension خوانده میشود:
Today Extension را انتخاب کرده و روی Next کلیک کنید:
یک نام به آن بدهید و زبان ترجیحی خود را انتخاب کنید. در این مورد، از سوئیفت استفاده میکنیم. روی Finish کلیک کنید:
اگر Xcode از شما خواست scheme را فعال کنید، روی Activate کلیک کنید. اکنون باید پوشه ویجت را در پروژه خود ببیند:
در این مرحله ویجت باید روی صفحه نیز دیده شود و در واقع آن را هم اینک فعال کردهاید:
اینک ساختار ویجت را بررسی میکنیم. به پوشه Widget بروید و روی فایل استوریبورد کلیک کنید. سپس روی دکمه assistant-editor یعنی دکمهای به صورت دو دایره تودرتو در گوشه راست-بالای صفحه کلیک کنید.
چنان که میبینید دو تابع viewDidLoad و widgetPerformUpdate وجود دارند. تابع viewDidLoad هر بار که کاربر به صفحه ویجت سوئیچ میکند، اجرا خواهد شد. بنابراین جایی است که باید متغیرها، برچسب (Label)-ها و نما (View)-ها مقداردهی شوند. تابع widgetPerformUpdate هر زمان که نیاز باشد محتوای ویجت بهروزرسانی شود، فراخوانی میشود.
سفارشیسازی UI ویجت
چنان که میبینید یک برچسب با عنوان Hello World وجود دارد و این برچسب را با کشیدن روی کد سفارشیسازی میکنیم. روی برچسب راست-کلیک کنید و Referencing Outlet جدید را مستقیماً روی کد و درون کلاس بکشید.
بدین ترتیب رفرنس در کد به صورت زیر دیده میشود:
متن برچسب را درون تابع viewDidLoad ویرایش میکنیم:
1override func viewDidLoad() {
2 super.viewDidLoad()
3 // Do any additional setup after loading the view from its nib.
4 //ADD THIS LINE
5 textLabel.text = "My first Widget"
6}
اگر اپلیکیشن را اجرا کنید، میبینید که برچسب به طرز صحیحی مقداردهی شده است:
نکته: ویجت را بدون اجرای کل اپلیکیشن از سوی Xcode و انتخاب کردن ویجت به عنوان هدف و سپس کلیک روی run اجرا میکنیم:
ایجاد کانال ارتباطی بین ویجت و اپلیکیشن ریاکت نیتیو
اینک بخش جالب ماجرا فرا رسیده است. تلاش میکنیم نمایش ویجت از سوی اپلیکیشن ریاکت نیتیو کنترل شود. به این منظور باید روشی برای ارتباط بین اپلیکیشن ریاکت نیتیو با ویجت پیادهسازی کنیم. قصد داریم این کار را از طریق یک حافظه مشترک بین ویجت و اپلیکیشن ریاکت نیتیو اجرا کنیم. این کار با استفاده از ماژول نیتیو iOS به نام UserDefaults میسر است.
بدین ترتیب از اپلیکیشن ریاکت نیتیو میخواهیم که مقادیر مورد نظر را در UserDefaults بنویسد. ویجت نیز از آن بخواند. مشکل نخست این است که هیچ روش رسمی برای ریاکت نیتیو جهت تعامل با UserDefaults معرفی نشده است و کتابخانه مناسبی نیز به این منظور وجود ندارد. بنابراین تلاش میکنیم این مورد را خودمان با استفاده از ایجاد پلی بین ریاکت نیتیو و iOS نیتیو پیادهسازی کنیم.
ابتدا یک فضای مشترک درون اپلیکیشن خود ایجاد میکنیم که امکان ارتباط بین ویجت و اپلیکیشن را میدهد. این کار از طریق App Groups ممکن است که در زبانه Capabilities قرار دارد.
آن را فعال میکنیم و سپس گروه را انتخاب میکنیم و یا در صورت خالی بودن، آن را اضافه میکنیم:
اکنون که App Groups فعال شده است، روشی پیادهسازی میکنیم که ریاکت نیتیو با ایجاد پل نیتیو در UserDefaults بنویسد. پروژه خود را انتخاب کرده و روی افزودن فایل جدید کلیک کنید:
سپس Cocoa Touch Class را انتخاب کرده و روی Next کلیک کنید:
از آنجا که این حافظهای است که از سوی ویجت و اپلیکیشن ریاکت نیتیو به اشتراک گذاشته شده است، آن را SharedStorage مینامیم. در ادامه گزینه Objective-C را انتخاب کرده و روی Next کلیک کنید:
اکنون باید فایلهای جدید را در پروژه خود ببینید:
در این مرحله فایلها را ویرایش میکنیم. ابتدا کد زیر را در فایل SharedStorage.h کپی کنید:
1//
2// SharedStorage.h
3// hodl
4//
5// Created by Andre Pimenta on 19/09/2018.
6// Copyright © 2018 Facebook. All rights reserved.
7//
8
9#import "React/RCTBridgeModule.h"
10
11@interface SharedStorage : NSObject <RCTBridgeModule>
12
13@end
کد زیر را نیز به فایل SharedStorage.m اضافه کنید:
1//
2// SharedStorage.m
3// hodl
4//
5// Created by Andre Pimenta on 19/09/2018.
6// Copyright © 2018 Facebook. All rights reserved.
7//
8
9#import "SharedStorage.h"
10#import "React/RCTLog.h"
11
12@implementation SharedStorage
13
14RCT_EXPORT_MODULE();
15
16// We can send back a promise to our JavaScript environment :)
17RCT_EXPORT_METHOD(set:(NSString *)data
18 resolver:(RCTPromiseResolveBlock)resolve
19 rejecter:(RCTPromiseRejectBlock)reject)
20{
21 @try{
22 //CHANGE THE GROUP HERE
23 NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@"group.com.createwidget.pimenta"];
24 [shared setObject:data forKey:@"data"];
25 [shared synchronize];
26 resolve(@"true");
27 }@catch(NSException *exception){
28 reject(@"get_error",exception.reason, nil);
29 }
30
31}
32
33@end
نکته مهم: نام گروه را از group.com.createwidget.pimenta به نامی که در زمان ایجاد App Groups وارد کردید، عوض کنید.
اکنون باید بتوانید SharedStorage را روی ریاکت نیتیو فراخوانی کنید. چنان که در کد میبینید، تنها کاری که انجام میدهد، این است که یک JSON دریافت و آن را روی حافظه UserDefaults ذخیره میکند.
کنترل محتوای ویجت با اپلیکیشن ریاکت نیتیو
در سمت ریاکت نیتیو، ماژول را ایمپورت میکنیم:
1import { NativeModules } from 'react-native';
2const SharedStorage = NativeModules.SharedStorage;
سپس برخی دادهها را به حافظه اضافه میکنیم:
1SharedStorage.set(
2 JSON.stringify({text: 'This is data from the React Native app'})
3);
این کار، برای نمونه روی App.js، یا هر جای دیگری که برای تعیین دادهها در کد ریاکت نیتیو مناسب تشخیص دهید قابل انجام است:
اکنون تنها کاری که مانده این است که از ویجت بخواهیم دادهها را خوانده و آنها را در UI درج کند. ما قصد داریم ویجت را به UserDefaults وصل کنیم، دادههایش را بخوانیم و سپس دادهها را روی برچسب textLabel با نام Hello World نمایش دهیم. به این منظور به فایل TodayViewController.swift در پوشه ویجت بروید و تابع viewDidLoad را به صورت زیر ویرایش کنید:
1//CHANGE THE GROUP NAME
2let userDefaults = UserDefaults(suiteName:"group.com.createwidget.pimenta")
3override func viewDidLoad() {
4 super.viewDidLoad()
5 // Do any additional setup after loading the view from its nib.
6 //ADD THIS
7 do{
8 let shared = userDefaults?.string(forKey: "data")
9 if(shared != nil){
10 let data = try JSONDecoder().decode(Shared.self, from: shared!.data(using: .utf8)!)
11 textLabel.text = data.text
12 }
13 }catch{
14 print(error)
15 }
16}
اینک کل فایل TodayViewController.swift باید به صورت زیر در آمده باشد:
1//
2// TodayViewController.swift
3// Widget
4//
5// Created by Andre Pimenta on 28/07/2019.
6// Copyright © 2019 Facebook. All rights reserved.
7//
8import UIKit
9import NotificationCenter
10
11struct Shared:Decodable {
12 let text: String
13}
14
15class TodayViewController: UIViewController, NCWidgetProviding {
16 @IBOutlet weak var textLabel: UILabel!
17
18 //CHANGE THE GROUP NAME
19 let userDefaults = UserDefaults(suiteName: "group.com.createwidget.pimenta")
20
21 override func viewDidLoad() {
22 super.viewDidLoad()
23 // Do any additional setup after loading the view from its nib.
24
25 //ADD THIS
26 do{
27 let shared = userDefaults?.string(forKey: "data")
28 if(shared != nil){
29 let data = try JSONDecoder().decode(Shared.self, from: shared!.data(using: .utf8)!)
30 textLabel.text = data.text
31 }
32 }catch{
33 print(error)
34 }
35 }
36
37 func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
38 // Perform any setup necessary in order to update the view.
39
40 // If an error is encountered, use NCUpdateResult.Failed
41 // If there's no update required, use NCUpdateResult.NoData
42 // If there's an update, use NCUpdateResult.NewData
43
44 completionHandler(NCUpdateResult.newData)
45 }
46
47}
نکته مهم: نام گروه group.com.createwidget.pimenta را به نامی که در زمان ایجاد App Groups وارد کردید، عوض کنید.
اینک اپلیکیشن را اجرا میکنیم و ویجت را مورد بررسی قرار میدهیم. توجه کنید که باید اپلیکیشن را اجرا کرده و باز کنید تا بتوانید در SharedStorage بنویسد:
اینک بخش iOS ما به پایان رسیده است.
اندروید
در این بخش به کدنویسی بخش اندروید میپردازیم.
ایجاد فایلهای ویجت
پوشه اندروید را در اندروید استودیو باز کنید. سپس به اندروید استودیو بروید و روی res > New > Widget > App Widget راست-کلیک کنید:
ویجت خود را نامگذاری و پیکربندی کرده و سپس روی Finish کلیک کنید:
پنجره بعدی چندین فایل را نمایش میدهد که باید به پروژه اضافه شوند. فایل Widget.java جایی است که رفتار ویجت را کدنویسی میکنیم. بقیه فایل نیز به پیادهسازی کامپوننتهای UI ویجت اختصاص دارد. روی Add کلیک کنید:
اکنون اگر اپلیکیشن را اجرا کنید تا ویجت را ببینید. توجه کنید که اگر ابتدا بخش iOS را اجرا کرده باشید، خطوطی که در کد ریاکت نیتیو اقدام به فراخوانی SharedStorage میکنند را کامنت کنید، زیرا هنوز در اندروید پیادهسازی نشده است و موجب خطا میشود.
اینک ویجت ما اجرا شده است. در ادامه آن را اندکی سفارشیسازی میکنیم.
سفارشیسازی UI ویجت
در اندروید استودیو، پوشه اپلیکیشن را باز کنید و فایل زیر را انتخاب کنید:
res -> layout -> widget.xml
بدین ترتیب لیآوت ویجت باز میشود. چنان که میبینید، یک نمای متنی با عبارت EXAMPLE است. در صورتی که روی آن کلیک کنید، میتوانید جزییات بیشتری ببینید:
متن برچسب را عوض میکنیم. چنان که در مشخصات برچسب میبینید، متن به string/appwidget_text@ ارجاع میدهد. این مقدار در res > values > strings.xml قرار دارد. این فایل را باز کنید و متن تعریف شده را بیابید:
اینک این متن را از EXAMPLE به HELLO عوض کنید. سپس فایل را ذخیره کرده و اپلیکیشن را بار دیگر اجرا کنید. این وضعیت در ویجت به صورت زیر بازتاب مییابد:
اکنون میدانیم که چگونه ویجت را سفارشیسازی کنیم. در بخش بعدی روشی برای انجام این کار از طریق اپلیکیشن ریاکت نیتیو پیادهسازی میکنیم.
ایجاد کانال ارتباطی بین ویجت و اپلیکیشن ریاکت نیتیو
در این بخش کاری انجام میدهیم که نمایش ویجت از سوی اپلیکیشن ریاکت نیتیو کنترل شود. به این منظور باید روشی پیادهسازی کنیم که اپلیکیشن ریاکت نیتیو با ویجت ارتباط بگیرد. ما قصد داریم این کار را از طریق یک فضای مشترک بین ویجت و اپلیکیشن ریاکت نیتیو انجام دهیم این کار با استفاده از یک ماژول نیتیو اندروید به نام SharedPreferences انجام مییابد.
ابتدا کاری میکنیم که اپلیکیشن ریاکت نیتیو در SharedPreferences بنویسد و ویجت از آن بخواند. مشکل نخست این است که هیچ روش رسمی در ریاکت نیتیو برای تعامل با SharedPreferences وجود ندارد. کتابخانه مناسبی نیز به این منظور وجود ندارد. بنابراین مجبور هستیم خودمان یک روش پیادهسازی کنیم. برای فراخوانی SharedPreferences یک پل بین ریاکت نیتیو و اندروید نیتیو میسازیم. همچنین دو فایل در پروژه در کنار MainActivity.java به نامهای SharedStorage.java و SharedStoragePackager.java ایجاد میکنیم:
کد زیر را به فایل SharedStoragePackager.java کپی کنید:
1//PUT YOUR PACKAGE NAME HERE, IT'S THE SAME AS IN MainApplication.java
2package com.reactnativecreatewidgettutorial;
3
4import com.facebook.react.ReactPackage;
5import com.facebook.react.bridge.JavaScriptModule;
6import com.facebook.react.bridge.NativeModule;
7import com.facebook.react.bridge.ReactApplicationContext;
8import com.facebook.react.uimanager.ViewManager;
9
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.List;
13
14public class SharedStoragePackager implements ReactPackage {
15
16 @Override
17 public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
18 return Collections.emptyList();
19 }
20
21 @Override
22 public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
23 List<NativeModule> modules = new ArrayList<>();
24
25 modules.add(new SharedStorage(reactContext));
26
27 return modules;
28 }
29
30}
کد زیر را نیز به فایل SharedStorage.java اضافه کنید:
1//PUT YOUR PACKAGE NAME HERE, IT'S THE SAME AS IN MainApplication.java
2package com.reactnativecreatewidgettutorial;
3
4import com.facebook.react.bridge.NativeModule;
5import com.facebook.react.bridge.ReactApplicationContext;
6import com.facebook.react.bridge.ReactContext;
7import com.facebook.react.bridge.ReactContextBaseJavaModule;
8import com.facebook.react.bridge.ReactMethod;
9
10import android.app.Activity;
11import android.appwidget.AppWidgetManager;
12import android.content.ComponentName;
13import android.content.Context;
14import android.content.Intent;
15import android.content.SharedPreferences;
16import android.util.Log;
17
18public class SharedStorage extends ReactContextBaseJavaModule {
19 ReactApplicationContext context;
20
21 public SharedStorage(ReactApplicationContext reactContext) {
22 super(reactContext);
23 context = reactContext;
24 }
25
26 @Override
27 public String getName() {
28 return "SharedStorage";
29 }
30
31 @ReactMethod
32 public void set(String message) {
33 SharedPreferences.Editor editor = context.getSharedPreferences("DATA", Context.MODE_PRIVATE).edit();
34 editor.putString("appData", message);
35 editor.commit();
36
37 //CHANGE TO THE NAME OF YOUR WIDGET
38 Intent intent = new Intent(getCurrentActivity().getApplicationContext(), Widget.class);
39 intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
40 //CHANGE TO THE NAME OF YOUR WIDGET
41 int[] ids = AppWidgetManager.getInstance(getCurrentActivity().getApplicationContext()).getAppWidgetIds(new ComponentName(getCurrentActivity().getApplicationContext(), Widget.class));
42 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
43 getCurrentActivity().getApplicationContext().sendBroadcast(intent);
44
45 }
46}
نکته مهم: نام پکیج را از com.reactnativecreatewidgettutorial به نام مورد نظر خود تغییر دهید. همچنین نام Widget.class را به نام کلاس ویجت خود عوض کنید. اکنون میتوانید SharedStorage را روی ریاکت نیتیو فراخوانی کنید. چنان که در کد میبینید، تنها کاری که انجام میدهد، دریافت JSON و ذخیره آن در فضای SharedPreferences و سپس اعلان بهروزرسانی به ویجت است. برای این که اندروید بداند ماژولهای شما وجود دارند، باید آنها را به لیست پکیجها در فایل MainActivity.java اضافه کنید:
new SharedStoragePackager()
کنترل محتوای ویجت با اپلیکیشن ریاکت نیتیو
در سمت ریاکت نیتیو باید ماژول را ایمپورت کنیم.
نکته: اگر کد ریاکت نیتیو را از قبل ویرایش کردهاید، میتوانید این بخش را رد کنید:
1import { NativeModules } from 'react-native';
2const SharedStorage = NativeModules.SharedStorage;
سپس دادههایی به فضای ذخیرهسازی اضافه میکنیم:
1SharedStorage.set(
2 JSON.stringify({text: 'This is data from the React Native app'})
3);
این کار برای نمونه میتواند در فایل App.js یا هر جای دیگری که برای تعیین دادهها روی کد ریاکت نیتیو مناسب تشخیص میدهید، انجام شود:
اینک تنها بخش باقیمانده این است که کاری کنیم ویجت، دادهها را بخواند و آن را در UI درج کند. ما میخواهیم ویجت با SharedPreferences متصل شود، دادههای آن را بخواند و سپس دادهها را روی برچسب HELLO پرینت کند.
به این منظور به فایل Widget.java در پوشه Widget بروید و ماژولهای زیر را ایمپورت کنید:
1import android.content.SharedPreferences;
2import org.json.JSONException;
3import org.json.JSONObject;
سپس تابع updateAppWidget را به صورت زیر ویرایش کنید:
1static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
2
3 try {
4 SharedPreferences sharedPref = context.getSharedPreferences("DATA", Context.MODE_PRIVATE);
5 String appString = sharedPref.getString("appData", "{\"text\":'no data'}");
6 JSONObject appData = new JSONObject(appString);
7 // Construct the RemoteViews object
8 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
9 views.setTextViewText(R.id.appwidget_text, appData.getString("text"));
10 // Instruct the widget manager to update the widget
11 appWidgetManager.updateAppWidget(appWidgetId, views);
12 }catch (JSONException e) {
13 e.printStackTrace();
14 }
15}
کد کامل فایل Widget.java به صورت زیر است:
1package com.reactnativecreatewidgettutorial;
2
3import android.appwidget.AppWidgetManager;
4import android.appwidget.AppWidgetProvider;
5import android.content.Context;
6import android.widget.RemoteViews;
7import android.content.SharedPreferences;
8
9import org.json.JSONException;
10import org.json.JSONObject;
11/**
12 * Implementation of App Widget functionality.
13 */
14public class Widget extends AppWidgetProvider {
15
16 static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
17 int appWidgetId) {
18
19 try {
20 SharedPreferences sharedPref = context.getSharedPreferences("DATA", Context.MODE_PRIVATE);
21 String appString = sharedPref.getString("appData", "{\"text\":'no data'}");
22 JSONObject appData = new JSONObject(appString);
23
24 // Construct the RemoteViews object
25 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
26 views.setTextViewText(R.id.appwidget_text, appData.getString("text"));
27 // Instruct the widget manager to update the widget
28 appWidgetManager.updateAppWidget(appWidgetId, views);
29 }catch (JSONException e) {
30 e.printStackTrace();
31 }
32 }
33
34 @Override
35 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
36 // There may be multiple widgets active, so update all of them
37 for (int appWidgetId : appWidgetIds) {
38 updateAppWidget(context, appWidgetManager, appWidgetId);
39 }
40 }
41
42 @Override
43 public void onEnabled(Context context) {
44 // Enter relevant functionality for when the first widget is created
45 }
46
47 @Override
48 public void onDisabled(Context context) {
49 // Enter relevant functionality for when the last widget is disabled
50 }
51}
تابع updateAppWidget را که مسئول بهروزرسانی محتوای ویجت است را ویرایش میکنیم تا ویجت بتواند دادهها را از پایگاه داده SharedPreferences بخواند و دادهها را بروی برچسب متنی نمایش دهد. اینک اپلیکیشن ریاکت نیتیو، محتوای ویجت را کنترل میکند. در این مرحله اپلیکیشن را اجرا میکنیم و ویجت را مورد بررسی قرار میدهیم. توجه کنید که باید اپلیکیشن را اجرا کرده و باز کنید تا بتواند در SharedStorage بنویسد.
اینک کدنویسی ما به پایان رسیده است. در این مرحله شما مبنایی به دست آوردهاید که میتوانید بقیه بخشهای ویجت را بر پایه آن بنویسید. نتیجه نهایی به صورت زیر است.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- مجموعه آموزشهای برنامهنویسی اندروید
- آموزش مقدماتی فریمورک React Native برای طراحی نرم افزارهای اندروید و iOS با زبان جاوا اسکریپت
- آموزش سوئیفت (Swift) — مجموعه مقالات مجله فرادرس
- کتابخانه React Native Navigation — راهنمای شروع به استفاده
==
من میخواستم یه برنامه درست کنم
که یه ویجت داشته باشه و اون ویجت از توی دیتابیس متن بگیره و متن توی ویجت هر ۲۴ ساعت یک بار تغییر کنه
ممنون میشم کمکم کنید