背景
嘗試使用Flutter來開發雙端應用,實現訂閱功能. 之前用kotlin實現過一次可以參考: koltin回撥地獄的一種解決思路.
實現過程
UI
這個UI比較簡單
訂閱邏輯
同樣使用流式來實現,主動丟擲異常來中斷流.
void _goBuy(SubscriptModel model) async {
setState(() {
_isloading = true;
});
// 購買前先確認是不是已經付費過,但是本地訂單丟失了
await FlutterInappPurchase
.instance
// 1. 建立和GP/AppStore的連線
.initConnection
// 2. 查詢歷史快取訂單,看看是否已經是vip了. 換機器可以restore,沒必要走網路查詢
.then((it) {
if (it == null) {
throw InitExection;
}
return FlutterInappPurchase.instance
.getAvailablePurchases()
.then((historyItems) => model.checkHistorySubscript(historyItems))
.then((item) {
if (item != null) {
model.updatePurchased(item);
throw VipStateException;
}
});
})
// 3. 獲取可購買產品列表
.then((it) {
return FlutterInappPurchase.instance
.getSubscriptions(List.filled(1, this._productID))
.then((items) => items
.firstWhere((item) => item.productId == this._productID));
})
// 4. 購買
.then((item) {
return FlutterInappPurchase.instance
.requestSubscription(this._productID)
.then((itemStr) => PurchasedItem.fromJSON(jsonDecode(itemStr)));
})
// 5.校驗
.then((purchaseItem) => model.checkSubscript(purchaseItem))
// 6.客戶端確認訂單
.then((purchaseItem) {
model.updatePurchased(purchaseItem);
SubscriptModel.log(StatisticConstant.SUCCESS);
FlutterInappPurchase.instance.consumePurchaseAndroid(
purchaseItem.purchaseToken, purchaseItem.isAcknowledgedAndroid);
})
// 異常處理
.catchError((e) {
switch (e.runtimeType) {
case PlatformException:
String method = e.code;
if (method == "service_error") {
_dismissLoading(
'''Google services are unavailable. Install "GoogleService" and communicate again.''');
} else if (method == "buyItemByType") {
_dismissLoading("");
}
break;
case VipStateException:
SubscriptModel.data[StatisticConstant.REASON] = 'isVip';
_dismissLoading('''Already is VIP. No repurchase required.''');
break;
case InitExection:
_dismissLoading(
'''Google services are unavailable. Install "GoogleService" and communicate again.''');
break;
}
})
.whenComplete(() => {_dismissLoading("")});
}
複製程式碼
這裡實現過程和kotlin原生實現思路一樣,程式碼風格都很接近,接收起來相對容易.同樣實現流式的問題也就轉化換成了:
- 怎麼把原生程式碼的回撥轉化成Future流
- 怎麼處理原生回撥哪些異常場景.
把原生程式碼的回撥轉化成Future
在kotlin,已經試用使用通道把java回撥轉化成suspend函式. 這裡也是一樣的,只是多了一層dart封裝.例如上面的initConnection方法先申明為一個Future函式
Future<String> get initConnection async {
if (_platform.isAndroid) {
await _setPurchaseListener();
String result =
await _channel.invokeMethod('initConnection').catchError((e) {
return null;
});
return result;
} else if (_platform.isIOS) {
await _setPurchaseListener();
final String result =
await _channel.invokeMethod('canMakePayments').catchError((e) {
return null;
});
return result;
}
throw PlatformException(
code: _platform.operatingSystem, message: "platform not supported");
}
複製程式碼
iOS和android分別實現initConnection 和 canMakePayments方法. 因為flutter是單執行緒模型,await和kotlin協程的await一樣是非阻塞的,原生實現功能後可以直接利用方法通道,結束await.
方法通道的原生java程式碼:
@Override
public void onMethodCall(final MethodCall call, final Result result) {
if (call.method.equals("initConnection")) {
billingClient = BillingClient.newBuilder(reg.activity()).setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
int responseCode = billingResult.getResponseCode();
if (responseCode == BillingClient.BillingResponseCode.OK) {
result.success("Billing client ready");
} else {
result.error(call.method, "responseCode: " + responseCode, "");
}
}
});
}
}
複製程式碼
這裡result就是起了通道的作用.
異常機制
Future回撥有個catchError函式,用法和kotlin的Exceptionhandler相似.比較下兩種程式碼
.catchError((e) {
switch (e.runtimeType) {
case PlatformException:
String method = e.code;
if (method == "service_error") {
_dismissLoading(
'''Google services are unavailable. Install "GoogleService" and communicate again.''');
} else if (method == "buyItemByType") {
_dismissLoading("");
}
break;
case VipStateException:
SubscriptModel.data[StatisticConstant.REASON] = 'isVip';
_dismissLoading('''Already is VIP. No repurchase required.''');
break;
case InitExection:
_dismissLoading(
'''Google services are unavailable. Install "GoogleService" and communicate again.''');
break;
}
})
複製程式碼
vs
when (throwable) {
is UserCancleException -> {
// 使用者取消
}
is SubscriptProductException -> {
// 訂閱購買異常
}
is InitException -> {
// GP初始化異常 一般是沒有裝GP
}
is RepeateSubscription -> {
// 重複購買
}
is NoProducteException -> {
// 找不到要購買的商品資訊
}
is AcknowException -> {
// 訂單確認異常
}
複製程式碼
總結
- 基於同樣一個業務場景,整體程式碼結構dart和koltin二者相似
- 把非同步callback轉化成同步程式碼的方式相似
- 異常機制處理相似
後續
- 把iOS端留下的問題補充好.
- 整理下Flutter的事件模型.