Cập nhật ứng dụng React Native thông qua code-push thay vì phải release lên app store No ratings yet.

Trong blog này, tôi sẽ chia sẻ kinh nghiệm của mình với CodePush trong React Native. Trước khi bắt đầu với CodePush, đây là phần giới thiệu nhanh về React Native và kiến ​​trúc của nó.

React Native là gì?

React Native là công nghệ được tạo bởi Facebook, cho phép các dev sử dụng JavaScript để làm mobile apps trên cả Android và iOS với cảm nhận và giao diện native. Một ví dụ để làm rõ cách vận hành của React Native là wrapper của codenative.

Kiến trúc đơn giản:

Một ứng dụng RN bao gồm các tệp JavaScript và bất kỳ file hình ảnh nào được đóng gói cùng nhau bởi packager (một tệp có tên là “android.js.bundle” hoặc “ios.js.bndle).

Tệp này được đóng gói trong tệp nhị phân cụ thể cho từng nền tảng (tệp .ipa hoặc .apk).

Kiến trúc Native React đơn giản (copyright React Native Team)

Vấn đề với việc cập nhật

Trong dự án của chúng tôi, chúng tôi đã gặp phải vấn đề với việc cập nhật ứng dụng. Ứng dụng của chúng tôi là ứng dụng hẹn hò hiện đang có hơn nửa triệu users và hơn 50 nghìn người dùng thường xuyên, để cho user gặp lỗi là ác mộng đối với đội phát triển sản phẩm. Chúng tôi đã hoàn thành tất cả các tính năng và ứng dụng đã tải lên cho các app store  tương ứng và đang đợi review, nhưng sau đó chúng tôi đã nhận ra rằng chúng tôi đã quên validate dữ liệu khi user đăng nhập (chỉ là ví dụ :D), việc này có thể dẫn đến việc ứng dụng của Client bị crash do lỗi undefined :(. Những ứng dụng truyền thống thường dùng là:

  1. Cung cấp ngay lập tức một bản vá lỗi sau đấy release một bản .ipa hoặc .apk để submit lại lên app store
  2. Chờ đợi ứng dụng đc upload lên store và sau đấy chờ cho quá trình xem xét hoàng thành. Quá trình này có thể lên đến vài ngày, tuỳ trường hợp, có khi còn bị reject 🙁 🙁 🙁

Rất là mất thời gian, trong thời gian này hàng nghìn users đã có thể gặp lỗi và app bị Crash, dẫn tới ảnh hướng đến trải nghiệm người dùng -> mất user 🙁 🙁 🙁

Giải pháp đó là code-push

Một App Center dịch vụ đám mây của Microsoft. (App liên quan đến Microsoft thì khỏi phải nghi ngờ :D)

 cho phép các nhà phát triển Cordova và React Native triển khai các bản cập nhật ứng dụng dành cho thiết bị di động trực tiếp đến thiết bị của người dùng mà không cần trợ giúp của Cửa hàng (cập nhật OTA)

 hữu ích hơn để sửa lỗi hoặc thêm / xóa các tính năng nhỏ không yêu cầu chúng tôi xây dựng lại nhị phân và phân phối lại thông qua các cửa hàng tương ứng.

Code-push hoạt động như thế nào?

CodePush hoạt động như thế nào? (bản quyền hình ảnh: Microsoft)

  • Nó hoạt động như một kho lưu trữ trung tâm mà nhà phát triển có thể đẩy các bản cập nhật và các ứng dụng có thể truy vấn và tải xuống từ đó – với sự trợ giúp của SDK tương ứng
  • Plugin này giúp người dùng cuối để có được các bản cải tiến ứng dụng / sửa lỗi ngay lập tức, bằng cách giữ JavaScript và hình ảnh  được đồng bộ hóa với các bản cập nhật mà chúng tôi phát hành cho máy chủ CodePush.
  • Vì nó duy trì bản sao của bản cập nhật trước, chúng ta có thể tự động rollback về trạng thái trước đó nếu chúng ta  phát hành bản cập nhật mới với lỗi / sự cố.

Lưu ý

  • Bất kỳ thay đổi sản phẩm nào động chạm vào native code (ví dụ: sửa đổi tệp AppDelegate.m / MainActivity.java của bạn, thêm plugin mới liên quan đến việc chỉnh sửa các file core native) không thể được phân phối qua CodePush và do đó, phải được cập nhật qua App store
  • Ngoài ra nó hỗ trợ từ iOS (7+) Android (4.1+)

Cài đặt vừa sử dụng Code-push

  1. Cài đặt CodePush CLI:

npm install -g code-push-cli

2. Tạo tài khoản CodePush bằng CLI:

code-push register

3. Đăng ký ứng dụng với CodePush:

code-push app add MyApp android react-native

Tích hợp với React Native:

Cộng đồng nguồn mở của Microsoft cung cấp một package là vô cùng hữu dụng và dễ dàng tích hợp và sử dụng gọi là react-native-code-push

  1. Cài đặt React Native  CodePush:

npm install –save react-native-code-push

2. Tích hợp với React Native,

a. sử dụng RNPM,

react-native link react-native-code-push

b. CocoaPods (chỉ dành cho iOS),

pod ‘CodePush’, :path => ‘../node_modules/react-native-code-push’

c. Thủ công (theo gradle cho android, thêm gói lib vào iOS)

Để có tài liệu tích hợp và thiết lập đầy đủ, vui lòng tham khảo https://github.com/Microsoft/react-native-code-push

Note: Đảm bảo rằng bạn đã thêm  deployment keys vào các file tương ứng sau đây AndroidManifest.xml cho Android và  Info.plist cho iOS

Ví dụ cách sử dụng

Trong ví dụ này, ứng dụng di động giao tiếp với máy chủ CodePush thường xuyên, tải xuống bản cập nhật âm thầm và cài đặt nó. Vì vậy, các bản cập nhật mới available cho người dùng bất cứ khi nào ứng dụng được khởi động lại.

class MyApp extends Component {
  ...
  render(
    return(
      <View>....</View>
    )
  )
}

//set check frequency options
const codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_START };
const ReachApp = codePush(codePushOptions)(Reach);

AppRegistry.registerComponent('Reach', () => ReachApp);

Một số tùy chọn CodePush

React Natvie Code Push cung cấp một số tuỳ chọn dưới đây.

  1. checkFrequency: tùy chọn để biểu thị khi bạn muốn kiểm tra các bản cập nhật. Các tùy chọn như ‘ON_APP_START, ON_APP_RESUME, MANUAL`
  2. installMode: tùy chọn để biểu thị khi bạn muốn cài đặt các bản cập nhật. `IMMEDIATE, ON_NEXT_RESTART, ON_NEXT_RESUME, ON_NEXT_SUSPEND`
  3. sync (): Phương thức này đồng bộ hóa các gói JavaScript và các phần tử hình ảnh của ứng dụng của chúng tôi với bản phát hành mới nhất cho việc triển khai được cấu hình. Hoạt động trên hai chế độ khác nhau ‘ Silent ‘ và ‘ Active ‘.
  4. syncStatus: Bạn có thể tìm trạng thái đồng bộ hóa bất kỳ lúc nào. Nó bao gồm các giá trị sau đây như ‘UP_TO_DATE, UPDATE_IGNORED, UPDATE_INSTALLED, SYNC_IN_PROGRESS’

ví dụ cụ thể

import React, { Component } from 'react';
import {
  AppRegistry,
  Dimensions,
  Image,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

import CodePush from "react-native-code-push";

class App extends Component<{}> {
  constructor() {
    super();
    this.state = { restartAllowed: true };
  }

  codePushStatusDidChange(syncStatus) {
    switch(syncStatus) {
      case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
        this.setState({ syncMessage: "Checking for update." });
        break;
      case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
        this.setState({ syncMessage: "Downloading package." });
        break;
      case CodePush.SyncStatus.AWAITING_USER_ACTION:
        this.setState({ syncMessage: "Awaiting user action." });
        break;
      case CodePush.SyncStatus.INSTALLING_UPDATE:
        this.setState({ syncMessage: "Installing update." });
        break;
      case CodePush.SyncStatus.UP_TO_DATE:
        this.setState({ syncMessage: "App up to date.", progress: false });
        break;
      case CodePush.SyncStatus.UPDATE_IGNORED:
        this.setState({ syncMessage: "Update cancelled by user.", progress: false });
        break;
      case CodePush.SyncStatus.UPDATE_INSTALLED:
        this.setState({ syncMessage: "Update installed and will be applied on restart.", progress: false });
        break;
      case CodePush.SyncStatus.UNKNOWN_ERROR:
        this.setState({ syncMessage: "An unknown error occurred.", progress: false });
        break;
    }
  }

  codePushDownloadDidProgress(progress) {
    this.setState({ progress });
  }

  toggleAllowRestart() {
    this.state.restartAllowed
      ? CodePush.disallowRestart()
      : CodePush.allowRestart();

    this.setState({ restartAllowed: !this.state.restartAllowed });
  }

  getUpdateMetadata() {
    CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING)
      .then((metadata: LocalPackage) => {
        this.setState({ syncMessage: metadata ? JSON.stringify(metadata) : "Running binary version", progress: false });
      }, (error: any) => {
        this.setState({ syncMessage: "Error: " + error, progress: false });
      });
  }

  /** Update is downloaded silently, and applied on restart (recommended) */
  sync() {
    CodePush.sync(
      {},
      this.codePushStatusDidChange.bind(this),
      this.codePushDownloadDidProgress.bind(this)
    );
  }

  /** Update pops a confirmation dialog, and then immediately reboots the app */
  syncImmediate() {
    CodePush.sync(
      { installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true },
      this.codePushStatusDidChange.bind(this),
      this.codePushDownloadDidProgress.bind(this)
    );
  }

  render() {
    let progressView;

    if (this.state.progress) {
      progressView = (
        <Text style={styles.messages}>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
      );
    }

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to CodePush!
        </Text>
        <TouchableOpacity onPress={this.sync.bind(this)}>
          <Text style={styles.syncButton}>Press for background sync</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={this.syncImmediate.bind(this)}>
          <Text style={styles.syncButton}>Press for dialog-driven sync</Text>
        </TouchableOpacity>
        {progressView}
        <Image style={styles.image} resizeMode={Image.resizeMode.contain} source={require("./images/laptop_phone_howitworks.png")}/>
        <TouchableOpacity onPress={this.toggleAllowRestart.bind(this)}>
          <Text style={styles.restartToggleButton}>Restart { this.state.restartAllowed ? "allowed" : "forbidden"}</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={this.getUpdateMetadata.bind(this)}>
          <Text style={styles.syncButton}>Press for Update Metadata</Text>
        </TouchableOpacity>
        <Text style={styles.messages}>{this.state.syncMessage || ""}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    backgroundColor: "#F5FCFF",
    paddingTop: 50
  },
  image: {
    margin: 30,
    width: Dimensions.get("window").width - 100,
    height: 365 * (Dimensions.get("window").width - 100) / 651,
  },
  messages: {
    marginTop: 30,
    textAlign: "center",
  },
  restartToggleButton: {
    color: "blue",
    fontSize: 17
  },
  syncButton: {
    color: "green",
    fontSize: 17
  },
  welcome: {
    fontSize: 20,
    textAlign: "center",
    margin: 20
  },
});

/**
 * Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a
 * different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not
 * need to be explicitly called. All options of CodePush.sync() are also available in this decorator.
 */
let codePushOptions = { checkFrequency: CodePush.CheckFrequency.MANUAL };

App = CodePush(codePushOptions)(App);

export default App;

Phát hành bản cập nhật

Sau khi đã chỉnh sửa code để sẵn sàng cung cấp một bản vá lỗi ngay lập tức đến người dùng, chỉ đơn giản chạy câu lệnh dưới đây từ thư mục root.

code-push release <appName> <updateContents> <targetBinaryVersion> [--deploymentName <deploymentName>] [--description <description>] [--disabled <disabled>] [--mandatory] --rollout <rolloutPercentage>]

Ví dụ cho MyApp Android:

Đây là lệnh để đẩy cập nhật lên máy chủ CodePush cho một ứng dụng Android có tên là “Myapp” chỉ là môi trường dàn dựng. Nó sẽ được chạy từ thư mục gốc của dự án và nó sẽ tự động build gói js và đẩy lên code push server.

code-push release-react MyApp android -d "Production" -m --description "Validate code in login to prevent Crash app"

Tùy chọn khi Release một bản vá lỗi

  1. deploymentName: Chỉ định môi trường ứng dụng là Production hay Staging
  2. description: Thêm ghi chú cho bản cập nhật
  3. rollout: Tùy chọn để tăng phần trăm phát hành của bản phát hành đích.
  4. mandatory: Tùy chọn để cho biết liệu bản phát hành có nên được xem là bắt buộc hay không.
  5. disabled: Tùy chọn để cập nhật xem bản phát hành có bị vô hiệu hóa hay không

Trên đây là tổng hợp lại một số kinh nghiệm & kiến thức của tôi trong việc sử dụng React Native Code Push trong việc cập nhật  nhanh những bản vá lỗi quan trọng. Trong quá trình hệ thống lại kinh nghiệm và kiến thức không tránh khỏi những sai sót, Rất mong các bạn đọc đóng góp ý kiến để Mình càng ngày càng hoàn thiện hơn bài viết. Bài viết được tham khảo từ một số nguồn dưới đây:

https://github.com/Microsoft/react-native-code-push

https://medium.com/spritle-software/react-native-codepush-b86f0ea8432c

Cám ơn các bạn đã quan tâm và đọc đến tận đây <3 <3 <3

Nếu bạn cảm thấy bài viết này hay và hữu ích thì hãy Like, Share & Subscribe techblog của bọn mình nhé 😀 😀

Please follow and like us:

Please rate this

You May Also Like

About the Author: duong

Leave a Reply

Your email address will not be published. Required fields are marked *