![Zakupy w aplikacji na Androida, część 2: przetwarzanie zakupów za pomocą Google Play Billing Library 12 62fdf1c4519df62803e8fafe jp android tutorial 1 configuration 2](https://adapty.io/wp-content/uploads/2023/01/62fdf1c4519df62803e8fafe_jp-android-tutorial-1-configuration-2.png)
Tutorial
7 września, 2022
Updated: 23 maja, 2023
23 min read
Międzyplatformowe frameworki tworzenia aplikacji z pewnością ułatwiają życie programistom, umożliwiając im tworzenie aplikacji na wiele platform jednocześnie. Są jednak też pewne ich wady. Na przykład React Native nie posiada gotowego narzędzia do wdrażania zakupów w aplikacji. Dlatego nieuchronnie będziesz musiał zwrócić się do bibliotek innych firm.
Popularne bibliotek dla subskrypcji aplikacji (in-app subscriptions) w aplikacjach React Native to react-native-iap i expo-in-app-purchases. Będę jednak opowiadać o react-native-adapty, ponieważ istnieje sporo korzyści korzystania właśnie z niej, w porównaniu do innych bibliotek:
Ale Adapty SDK to o wiele więcej. Otrzymujesz wbudowane narzędzia analityczne dla wszystkich kluczowych wskaźników, analizy kohortowej (cohort analysis), walidacji zakupów na serwerze (purchase validation), testów AB dla paywall (AB testing), kampanii promocyjnych z elastyczną segmentacją, integracji narzędzi analitycznych innych firm i wiele więcej.
Na razie porozmawiajmy o konfigurowaniu zakupów w aplikacji w aplikacjach natywnych React. O czym dziś porozmawiamy:
W tym przewodniku postaramy się stworzyć aplikację, która wyświetli zdjęcia kota subskrybowanym użytkownikom i zaproponuje wszystkim innym ofertę subskrypcji.
Krótko mówiąc: Expo „zarządzane” nie obsługuje natywnych metod przetwarzania zakupów w sklepach z aplikacjami (znanych również jako zestawy sklepowe). Musisz albo trzymać się czystego RN, albo używać prostego przepływu Expo.
Od razu muszę rozczarować tych, którzy myśleli o korzystaniu z Expo: to nie zadziała. Expo to framework React Native, który znacznie ułatwia tworzenie aplikacji. Ich zarządzany przepływ pracy nie jest jednak zgodny z przetwarzaniem zakupów/subskrypcji. Expo nie używa żadnego natywnego kodu w swoich metodach i komponentach (są bazowane na JS), co jest wymagane dla zestawów sklepowych. Nie ma sposobu na wdrożenie zakupów w aplikacji w sklepach mobilnych z JavaScript, więc będziesz musiał „wysuwać”.
Najpierw musisz skonfigurować konta w app store, a także utworzyć i skonfigurować zakupy oraz subskrypcje dla systemów iOS i Android. To nie powinno zająć więcej niż 20 minut.
Jeśli nadal nie skonfigurowałeś konta dewelopera i produktów w App Store Connect i/lub konsoli Google Play, zapoznaj się z tymi poradnikami:
W przypadku react-native-adapty musisz najpierw skonfigurować pulpit nawigacyjny Adapty. To nie zajmie dużo czasu, a zapewni Ci wszystkie zalety wymienione powyżej, które posiada Adapty w porównaniu do twardego kodowania.
W trzecim kroku pojawi się przypomnienie o konfiguracji App Store i Google Play.
W przypadku systemu iOS musisz:
Te pola są wymagane, aby zakupy działały.
Każde pole zawiera podpowiedzi „Przeczytaj, jak”, które zawierają instrukcje krok po kroku. Sprawdź je, jeśli masz jakieś pytania.
Bundle ID to unikalny identyfikator Twojej aplikacji. Musi pasować do tego, który podałeś w Xcode, w Targets > [App Name] > General:
W przypadku systemu Android wymagane pola to nazwa pakietu (Package Name) i plik klucza konta usługi (Service Account Key File). Wszystkie te pola mają swoje własne wskazówki, które również warto przeczytać. Nazwa pakietu w systemie Android działa tak, jak identyfikator pakietu w systemie iOS. Musi pasować do tego podanego w kodzie, który można znaleźć w pliku /android/app/build.gradle w android.defaultConfig.applicationId:
W czwartym kroku pojawi się żądanie podłączenia zestawu Adapty SDK do aplikacji. Pomiń na razie ten krok — wrócimy do tego nieco później.
Po zarejestrowaniu się sprawdź zakładkę ustawienia i pamiętaj, że tutaj można znaleźć publiczny klucz SDK. Będziesz potrzebować tego klucza później.
Adapty wykorzystuje produkty dla różnych subskrypcji. Twoja subskrypcja zdjęć kotów może być tygodniowa, półroczna lub roczna. Każda z tych opcji będzie osobnym produktem Adapty.
Określmy na pulpicie nawigacyjnym, że mamy jeden produkt. Aby to zrobić, przejdź do Products & A/B Tests → Products i kliknij Create product.
W tym miejscu musisz określić nazwę produktu, czyli sposób, w jaki ta subskrypcja będzie wyglądać na pulpicie nawigacyjnym Adapty.
Musisz również określić App Store Product ID i Play Store Product ID. Jeśli chcesz, określ okres i nazwę dla analityki. Zapisz zmiany (Save).
Teraz musisz zaprojektować paywall, który jest ekranem, ograniczającym dostęp Użytkownika do funkcji premium i wyświetlającym mu ofertę subskrypcji. Musisz dodać utworzony produkt do paywalla. Aby to zrobić, kliknij „Create paywall” w tej samej sekcji (Products & A/B Tests → Paywalls).
Kliknij zapisz i opublikuj.
To wszystko, jeśli chodzi o konfigurację. Teraz będziemy dodawać zależności i pisać kod.
1. Najpierw dodaj zależność:
yarn add react-native-adapty
2. Zainstaluj iOS pods. Jeśli nie masz jeszcze CLI pod, zdecydowanie polecam go pobrać. Na pewno będzie to bardzo potrzebne przy pracy z iOS.
#pods get installed into the native iOS project, which, by default, is the /ios folderpod install --project-directory=ios
3. Ponieważ projekty React Native iOS są pisane w Obj-C, musisz utworzyć Swift Bridging Header, aby Obj-C mógł czytać biblioteki Swift. Aby to zrobić, po prostu otwórz projekt Xcode i utwórz nowy plik Swift. Xcode zapyta, czy chcesz utworzyć nagłówek pomostowy (bridging header), który jest dokładnie tym, czego potrzebujesz. Kliknij utwórz (Create).
4. Dla Androida, upewnij się, że projekt – domyślnie /android/build.gradle używa wtyczki kotlin-gradle-plugin w wersji 1.4.0 lub nowszej:
... buildscript {
... dependencies {
... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
}
} ...
5. W przypadku Androida musisz włączyć multiDex, który można znaleźć w pliku konfiguracyjnym aplikacji (domyślnie /android/app/build.gradle.)
...
android {
... defaultConfig {
... multiDexEnabled true
}
}
Voila, wszystko gotowe, możesz odpocząć chwilę przed rozpoczęciem kodowania!
Jest mnóstwo przydatnych rzeczy dziejących się pod kuratelą react-native-adapty. Z pewnością będziesz ich potrzebował, prędzej czy później, dlatego powinieneś zainicjować bibliotekę na samym początku swojego działania. Idź tak wysoko, jak to tylko możliwe w kodzie aplikacji (możesz to zrobić bezpośrednio w aplikacji.tsx) i rozpocznij inicjalizację:
// import the method
import { activateAdapty } from 'react-native-adapty';
// We’ve had this App component in our app’s root
const App: React.FC = () => {
...
// we’re invoking it once in a root component on mount
useEffect(() => {
activateAdapty({ sdkKey: 'MY_PUBLIC_KEY' });
},[]);
...
}
Tutaj zastąp MY_PUBLIC_KEY publicznym kluczem SDK znalezionym w ustawieniach pulpitu nawigacyjnego. W rzeczywistości metoda activateAdapty() może być wywoływana więcej niż raz i w więcej niż jednym miejscu, ale pozostaniemy przy tej wersji.
Teraz możemy pobrać produkty, które dodaliśmy w panelu Adapty:
import { adapty } from 'react-native-adapty';
async function getProducts() {
const {paywalls, products} = await adapty.paywalls.getPaywalls();
return products;
}
Teraz przejdźmy do praktyki: spróbujemy zbudować małą aplikację, w której będziemy mogli przeglądać produkty z naszych paywall i dokonywać zakupów.
Od teraz będę się streszczać, żeby nie skomplikować niepotrzebnie logiki. Będę również kodować w TypeScript, aby pokazać, które typy są używane i gdzie. Do testowania użyję mojego starego, dobrego iPhone’a 8. Pamiętaj, że od iOS 14 App Store zabrania używania zestawów sklepowych w emulatorach— możesz testować tylko przy użyciu urządzeń fizycznych.
1. Najpierw stwórzmy komponent główny App.tsx, który będzie posiadał przycisk wyświetlania paywall. Skonfigurowaliśmy już nawigację przez react-native-navigation– uważamy, że jest to znacznie lepsze, niż opcja nawigacji react zalecana w oficjalnych dokumentach.
ISSUE
import React, { useEffect, useState } from "react";
import { Button, StyleSheet, View } from "react-native";
import { adapty, activateAdapty, AdaptyPaywall } from "react-native-adapty";
export const App: React.FC = () => {
const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);
useEffect(() => {
async function fetchPaywalls(): Promise<void> {
await activateAdapty({ sdkKey: "MY_PUBLIC_KEY" });
const result = await adapty.paywalls.getPaywalls();
setPaywalls(result.paywalls);
}
fetchPaywalls();
}, []);
return (
<View style={styles.container}>
<Button
title="Show the paywall"
onPress={() => {
const paywall = paywalls.find(
(paywall) => paywall.developerId === "cats_paywall"
);
if (!paywall) {
return alert("There is no such paywall");
}
// Switching to a paywall...
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
});
O co tu chodzi? Podczas montowania wywoływana jest funkcja fetchPaywalls (). Aktywuje SDK i zapisuje paywall w tym stanie, aby użytkownik nie musiał czekać na pobieranie po naciśnięciu przycisku. Widoczny jest tylko jeden przycisk, który ma przenieść użytkownika do paywalla, który wcześniej zaprojektowaliśmy na pulpicie nawigacyjnym.
Właściwie jest możliwe dostarczenie paywalli tutaj, bez zapisywania ich w stanie. Domyślnie adapty.paywalls.getPaywalls() pobierze je z pamięci podręcznej (po cachowaniu ich podczas uruchomienia), co oznacza, że nie będziesz musiał czekać na metodę, aby skomunikować się z serwerem.
Oto wynik:
2. Zapiszmy komponent paywall w tym samym pliku.
// there are more imports here
import React, { useEffect, useState } from "react";
import {
Button,
SafeAreaView,
StyleSheet,
Text,
View,
PlatformColor,
} from "react-native";
import {
adapty,
activateAdapty,
AdaptyPaywall,
AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";
// ...
interface PaywallProps {
paywall: AdaptyPaywall;
onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
return (
<SafeAreaView style={styles.container}>
{paywall.products.map((product) => (
<View key={product.vendorProductId}>
<Text>{product.localizedTitle}</Text>
<Button
title={`Buy for за ${product.localizedPrice}`}
disabled={isLoading}
onPress={async () => {
try {
setIsLoading(true);
await onRequestBuy(product);
} catch (error) {
alert("Error occured :(");
} finally {
setIsLoading(false);
}
}}
/>
</View>
))}
</SafeAreaView>
);
};
// A new key
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
paywallContainer: {
flex: 1,
alignItems: "center",
justifyContent: "space-evenly",
backgroundColor: PlatformColor("secondarySystemBackground"),
},
});
Tutaj po prostu zmapujemy produkty z paywall i wyświetlimy przycisk zakupu obok każdego produktu.
3. Aby zobaczyć, jak to wygląda, zarejestrujmy ten ekran w react-native-navigation. Jeśli używasz innej nawigacji, pomiń ten krok. Mój plik główny indeks.js wygląda tak:
import "react-native-gesture-handler";
import { Navigation } from "react-native-navigation";
import { App, Paywall } from "./App";
Navigation.registerComponent("Home", () => App);
Navigation.registerComponent("Paywall", () => Paywall);
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: { stack: { children: [{ component: { name: "Home" } }] } },
});
});
4. Teraz musimy tylko przypisać akcję do przycisku „Wyświetl paywall”. W naszym przypadku poprosi o modal poprzez nawigację.
Navigation.showModal<PaywallProps>({
component: {
name: "Paywall",
passProps: {
paywall,
onRequestBuy: async (product) => {
const purchase = await adapty.purchases.makePurchase(product);
// Doing everything we need
console.log("purchase", purchase);
},
},
},
});
Cały plik App.tsx
import React, { useEffect, useState } from "react";
import {
Button,
SafeAreaView,
StyleSheet,
Text,
View,
PlatformColor,
} from "react-native";
import {
adapty,
activateAdapty,
AdaptyPaywall,
AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";
export const App: React.FC = () => {
const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);
useEffect(() => {
async function fetchPaywalls(): Promise<void> {
await activateAdapty({
sdkKey: "MY_PUBLIC_KEY",
});
const result = await adapty.paywalls.getPaywalls();
setPaywalls(result.paywalls);
}
fetchPaywalls();
}, []);
return (
<View style={styles.container}>
<Button
title="Show paywall"
onPress={() => {
const paywall = paywalls.find(
(paywall) => paywall.developerId === "cats_paywall"
);
if (!paywall) {
return alert("There is no such paywall");
}
Navigation.showModal<PaywallProps>({
component: {
name: "Paywall",
passProps: {
paywall,
onRequestBuy: async (product) => {
const purchase = await adapty.purchases.makePurchase(product);
// Doing everything we need
console.log("purchase", purchase);
},
},
},
});
}}
/>
</View>
);
};
interface PaywallProps {
paywall: AdaptyPaywall;
onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
return (
<SafeAreaView style={styles.paywallContainer}>
{paywall.products.map((product) => (
<View key={product.vendorProductId}>
<Text>{product.localizedTitle}</Text>
<Button
title={`Buy for ${product.localizedPrice}`}
disabled={isLoading}
onPress={async () => {
try {
setIsLoading(true);
await onRequestBuy(product);
} catch (error) {
alert("Error occured :(");
} finally {
setIsLoading(false);
}
}}
/>
</View>
))}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
paywallContainer: {
flex: 1,
alignItems: "center",
justifyContent: "space-evenly",
backgroundColor: PlatformColor("secondarySystemBackground"),
},
});
O to chodziło! Teraz możesz wyświetlać te paywalle swoim użytkownikom.
Jeśli chcesz przetestować subskrypcję na iOS w środowisku sandbox, musisz utworzyć własne konto testera sandbox. Pamiętaj, że subskrypcje sandbox są szybko unieważniane,aby ułatwić testowanie. W przypadku Androida nie potrzebujesz żadnych dodatkowych kont – możesz nawet uruchamiać testy w emulatorze.
2024 subscription benchmarks and insights
Get your free copy of our latest subscription report to stay ahead in 2024.
Nadal musimy zdecydować, gdzie przechowywać aktywne dane subskrypcji, aby przyznać użytkownikowi końcowemu dostęp do jego treści premium. Adapty pomoże nam również w tym , ponieważ zapisuje wszystkie zakupy, związane z użytkownikiem. Zróbmy to w ten sposób: jeśli użytkownik nie ma subskrypcji, zostanie wyświetlony przycisk paywall. Jeśli ją posiada, pokażemy mu zdjęcie kota.
Ponieważ aktywne dane subskrypcji są pobierane z serwera lub pamięci podręcznej, potrzebny jest program ładujący. Dla uproszczenia dodajmy stany isLoading i isPremium.
// ...
export const App: React.FC = () => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isPremium, setIsPremium] = useState<boolean>(false);
const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);
useEffect(() => {
async function fetchPaywalls(): Promise<void> {
try {
await activateAdapty({
sdkKey: "MY_PUBLIC_KEY",
});
const profile = await adapty.purchases.getInfo();
const isSubscribed = profile.accessLevels.premium.isActive;
setIsPremium(isSubscribed);
if (!isSubscribed) {
const result = await adapty.paywalls.getPaywalls();
setPaywalls(result.paywalls);
}
} finally {
setIsLoading(false);
}
}
fetchPaywalls();
}, []);
// ...
}
// ...
Oto co się zmieniło: dodaliśmy twa oznaczenia do stanu. Cała zawartość fetchPaywalls() jest teraz otoczona blokadą try-catch, aby kod osiągnął setIsLoading (false) w każdym możliwym scenariuszu. Aby sprawdzić, czy użytkownik ma aktywną subskrypcję, pobieramy profil użytkownika (który zawiera wszystkie dane jego subskrypcji) i widzimy wartość profil.accessLevels.premium.isActive. Możesz użyć tylu poziomów dostępu (accessLevels), które są w zasadzie tylko stopniami subskrypcji, takimi jak Gold lub Premium, ilu potrzebujesz, ale na razie zachowajmy domyślną wartość. Adapty automatycznie utworzy poziom dostępu premium, a dla większości aplikacji to wystarczy. isActive pozostanie true, chociaż istnieje aktywna subskrypcja z tym poziomem dostępu
Od tego momentu wszystko wygląda całkiem prosto. Jeśli użytkownik ma status subskrypcji stopnia premium, nie ma potrzeby pobierania paywalla — wystarczy wyłączyć program ładujący i wyświetlić zawartość.
export const App: React.FC = () => {
// ...
const renderContent = (): React.ReactNode => {
if (isLoading) {
return <Text>Loading...</Text>;
}
if (isPremium) {
return (
<Image
source={{
url: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",
width: Dimensions.get("window").width * 0.8,
height: Dimensions.get("window").height * 0.8,
}}
/>
);
}
return (
<Button
title="Show paywall"
onPress={() => {
const paywall = paywalls.find(
(paywall) => paywall.developerId === "cats_paywall"
);
if (!paywall) {
return alert("There is no such paywall");
}
Navigation.showModal<PaywallProps>({
component: {
name: "Paywall",
passProps: {
paywall,
onRequestBuy: async (product) => {
const purchase = await adapty.purchases.makePurchase(product);
const isSubscribed =
purchase.purchaserInfo.accessLevels?.premium.isActive;
setIsPremium(isSubscribed);
Navigation.dismissAllModals();
},
},
},
});
}}
/>
);
};
return <View style={styles.container}>{renderContent()}</View>;
};
Tutaj dodajemy funkcję, która renderuje zawartość, a także pewną logikę do onRequestBuy: mianowicie aktualizowanie stanu isPremium i zamykanie modalu.
Tak wygląda efekt końcowy:
Cały plik:
import React, { useEffect, useState } from "react";
import {
Button,
SafeAreaView,
StyleSheet,
Text,
View,
PlatformColor,
Image,
Dimensions,
} from "react-native";
import {
adapty,
activateAdapty,
AdaptyPaywall,
AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";
export const App: React.FC = () => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isPremium, setIsPremium] = useState<boolean>(false);
const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);
useEffect(() => {
async function fetchPaywalls(): Promise<void> {
try {
await activateAdapty({
sdkKey: "MY_PUBLIC_KEY",
});
const profile = await adapty.purchases.getInfo();
const isSubscribed = profile.accessLevels.premium.isActive;
setIsPremium(isSubscribed);
if (!isSubscribed) {
const result = await adapty.paywalls.getPaywalls();
setPaywalls(result.paywalls);
}
} finally {
setIsLoading(false);
}
}
fetchPaywalls();
}, []);
const renderContent = (): React.ReactNode => {
if (isLoading) {
return <Text>Loading...</Text>;
}
if (isPremium) {
return (
<Image
source={{
uri: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",
width: Dimensions.get("window").width * 0.8,
height: Dimensions.get("window").height * 0.8,
}}
/>
);
}
return (
<Button
title="Show a paywall"
onPress={() => {
const paywall = paywalls.find(
(paywall) => paywall.developerId === "cats_paywall"
);
if (!paywall) {
return alert("There is no such a paywall");
}
Navigation.showModal<PaywallProps>({
component: {
name: "Paywall",
passProps: {
paywall,
onRequestBuy: async (product) => {
const purchase = await adapty.purchases.makePurchase(product);
const isSubscribed =
purchase.purchaserInfo.accessLevels?.premium.isActive;
setIsPremium(isSubscribed);
Navigation.dismissAllModals();
},
},
},
});
}}
/>
);
};
return <View style={styles.container}>{renderContent()}</View>;
};
interface PaywallProps {
paywall: AdaptyPaywall;
onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
return (
<SafeAreaView style={styles.paywallContainer}>
{paywall.products.map((product) => (
<View key={product.vendorProductId}>
<Text>{product.localizedTitle}</Text>
<Button
title={`Buy for ${product.localizedPrice}`}
disabled={isLoading}
onPress={async () => {
try {
setIsLoading(true);
await onRequestBuy(product);
} catch (error) {
alert("An error occured :(");
} finally {
setIsLoading(false);
}
}}
/>
</View>
))}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
paywallContainer: {
flex: 1,
alignItems: "center",
justifyContent: "space-evenly",
backgroundColor: PlatformColor("secondarySystemBackground"),
},
});
Stworzyliśmy całkiem nieźle wyglądającą i niezwykle przydatną aplikację subskrypcyjną. Osoby, które zapłacą, zobaczą koty, a wszystkim innym wyświetli się paywall. Ten przewodnik powinien nauczyć cię wszystkiego, czego możesz potrzebować, aby zaimplementować zakupy w aplikacji. A dla osób, które nie mogą się doczekać zagłębiania się w zestawy sklepowe – czekajcie na kolejne artykuły. Dzięki!
Recommended posts
Tutorial
7 września, 2022