BlogRight arrowTutorialRight ArrowAcquisti in-app React Native: implementazione semplice. Tutorial
BlogRight arrowTutorialRight ArrowAcquisti in-app React Native: implementazione semplice. Tutorial

Acquisti in-app React Native: implementazione semplice. Tutorial

Acquisti in-app React Native: implementazione  semplice. Tutorial
Listen to the episode
Acquisti in-app React Native: implementazione  semplice. Tutorial

I framework per lo sviluppo di applicazioni multipiattaforma semplificano certamente la vita degli sviluppatori, consentendo loro di creare applicazioni per più piattaforme contemporaneamente. Purtroppo, però, questi hanno anche dei lati negativi Ad esempio, React Native non ha uno strumento già pronto per implementare gli acquisti in-app . Di conseguenza, non potrai fare altro che avvalerti di librerie di terze parti. 

Quali sono le opzioni di implementazione degli acquisti in-app (in-app purchases implementation)?

Le librerie più diffuse per gli abbonamenti in-app nelle applicazioni React Native sono react-native-iap ed expo-in-app-purchases. Io, però, parlerò di react-native-adapty, poiché presenta diversi vantaggi, rispetto alle altre librerie:

  • A differenza dalle altre, fornisce una convalida degli acquisti (purchase validation) basata sul server.
  • Supporta tutte le funzionalità implementate di recente dagli app store, a partire dalle offerte promozionali (promo offers) alle funzionalità di pagamento anticipato (pay-upfront). Inoltre, è veloce nel supportare le nuove funzionalità in arrivo.
  • Il codice risulta più chiaro e lineare.
  • Puoi modificare la tua offerta di prodotti e aggiungere o rimuovere nuove offerte senza dover passare attraverso l'intero ciclo di rilascio. Non è necessario rilasciare versioni beta e attendere l'approvazione.

L'SDK di Adapty offre molto di più. Avrai a disposizione strumenti di analisi integrati per tutti i parametri chiave, analisi di coorte (cohort analysis), convalida degli acquisti basata su server, test AB (AB testing) per i paywall, campagne promozionali con segmentazione flessibile, integrazioni (integrations) con strumenti di analisi di terze parti e molto altro ancora.

In questo articolo

Per ora parliamo di come impostare gli acquisti in-app nelle app React Native- Ecco cosa tratteremo oggi:

  1. Perché Expo non funziona per gli acquisti in-app nelle applicazioni React Native.
  2. Creazione di un account di sviluppatore.
  3. Configurazione Adapty:
    Configurazione App Store
    Configurazione Play Store
  4. Aggiunta abbonamenti.
  5. Creazione di un paywall.
  6. Installazione di react-native-adapty.
  7. App di esempio e risultato. 

In questa guida, cercheremo di costruire un'app che mostri immagini di gatti agli utenti abbonati e che chieda a tutti gli altri un'offerta di abbonamento. 

⭐️ Download our guide on in-app techniques which will make in-app purchases in your app perfect

Perché Expo non funziona per gli acquisti in-app nelle applicazioni React Native

Per farla breve: Expo “gestito” non supporta i metodi nativi offerti dagli app store per l'elaborazione degli acquisti (noti anche come store kit (StoreKits)). È necessario attenersi al RN puro o utilizzare il flusso di lavoro bare di Expo.

Devo deludere sin da subito chi ha pensato di usare Expo: non funzionerà. Expo è un framework React Native che rende lo sviluppo delle app molto più semplice. Il loro flusso di lavoro gestito non è però compatibile con l'elaborazione di acquisti e abbonamenti. Expo non utilizza alcun codice nativo nei suoi metodi e componenti (entrambi sono solo JS), che è necessario per gli store kit. Non c'è modo di implementare gli acquisti in-app nei negozi mobili con JavaScript, quindi dovrai "espellere".

Creazione di un account di sviluppatore

Innanzitutto, è necessario impostare gli account degli app store, nonché creare e configurare acquisti e abbonamenti sia per iOS che per Android. Questa operazione non dovrebbe richiedere più di 20 minuti.

Se non hai ancora configurato l'account sviluppatore e i prodotti in App Store Connect e/o della Google Play Console, consulta le guide di seguito:

  • Per iOS: leggi la guida dall'inizio fino alla voce "Come ottenere l'elenco di SKProduct", perché è qui che si inizia a parlare delle implementazioni (implementations) native. 
  • Per  Android: leggi la guida dall'inizio fino alla voce "Come ottenere un elenco di prodotti in un'app".

Configurazione Adapty

Per react-native-adapty, occorre innanzitutto configurare la dashboard di Adapty. Questo non richiederà molto tempo, ma consentirà di ottenere tutti i vantaggi sopra elencati che Adapty offre rispetto alla codifica hardware.

Al terzo passaggio, ti verrà richiesto di configurare App Store e Google Play.

Per iOS, è necessario:

  • Specificare l'ID del bundle;
  • Configurare le notifiche del server (server notifications) App Store
  • Specificare il segreto condiviso (shared secret) di App Store Connect. 

Questi campi sono necessari per il funzionamento del processo d’acquisto. 

Ogni campo ha un suggerimento di consultazione che contiene guide passo. Per qualsiasi domanda, consulta queste pagine.

L’ID del bundle è l'ID univoco della tua app. Deve corrispondere a quello specificato in Xcode, in Targets > [App Name] > General:

Per Android, i campi richiesti sono il Nome del pacchetto e il File chiave dell'account di servizio. Anche questi campi hanno i loro suggerimenti di consultazione. Il nome del pacchetto fa in Android ciò che l'ID del bundle fa in iOS. Deve corrispondere a quello specificato nel codice, che si trova nel file /android/app/build.gradle in android.defaultConfig.applicationId:

Al quarto passaggio, ti verrà richiesto di collegare l'SDK di Adapty alla tua app. Per ora, però, saltiamo questo passaggio: ci torneremo più avanti.

Una volta effettuata la registrazione, controlla la scheda delle impostazioni e ricorda che è qui che si trova la chiave SDK pubblica. Ne avrai bisogno in seguito.

Aggiunta abbonamenti

Adapty utilizza prodotti per diversi abbonamenti. L'abbonamento alle tue foto di gatti può essere settimanale, semestrale o annuale. Ciascuna di queste opzioni sarà un prodotto Adapty separato.

Specifichiamo che abbiamo un prodotto nella dashboard. Per farlo, vai su Products & A/B Tests → Products e fai clic su Create product. 

Qui è necessario specificare il nome del prodotto, ovvero l'aspetto che l'abbonamento avrà nella dashboard di Adapty. 

È inoltre necessario specificare l'ID prodotto App Store e l'ID prodotto Play Store. Se lo desideri, specifica anche il periodo e il nome per l'analisi. Fai clic su Save.

Creazione di un paywall

A questo punto, dovrai progettare un paywall, ovvero una schermata che limiti l'accesso dell'utente alle funzioni premium e gli propone un'offerta di abbonamento. Dovrai aggiungere il prodotto creato al tuo paywall. Per farlo, fai clic su Create paywall nella stessa sezione (Products & A/B Tests → Paywalls).

  • Scegli un nome paywall dal quale tu e il tuo team possiate facilmente dedurre, semplicemente guardando il nome, di quale paywall si tratta.
  • L'ID del paywall verrà utilizzato per visualizzare il paywall nella tua app. Per la nostra app di esempio, useremo "cats_paywall".
  • Nel menu a tendina Product, seleziona l'abbonamento.

Fai clic su Save & publish.

La configurazione è terminata. Ora aggiungeremo le dipendenze e scriveremo il codice.

Installazione di react-native-adapty

1. Per prima cosa, aggiungi la dipendenza:

yarn add react-native-adapty

2. Installa i pod iOS. Se non hai ancora il pod CLI, ti consiglio vivamente di scaricarlo. Sicuramente ne avrai bisogno spesso, durante lo sviluppo per iOS.

#pods get installed into the native iOS project, which, by default, is the /ios folderpod install --project-directory=ios

3. Dal momento che i progetti iOS React Native sono scritti in Obj-C, è necessario creare un’intestazione di collegamento a Swift, in modo che Obj-C possa leggere le librerie Swift. Per farlo, basta aprire il progetto Xcode e creare un nuovo file Swift. Xcode chiederà se si desidera creare un'intestazione di collegamento, che è esattamente ciò vuoi. Fai clic su Create.

4. Per Android, assicurati che il progetto-/android/build.gradle, per impostazione predefinita, utilizzi il plugin kotlin-gradle della versione 1.4.0 o superiore:


... buildscript { 
  ... dependencies { 
    ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0" 
  } 
} ...

5. Per Android, avrai bisogno di abilitare multiDex, che si trova nel file di configurazione dell'app(/android/app/build.gradle per impostazione predefinita).


... 
android { 
  ... defaultConfig { 
    ... multiDexEnabled true 
  } 
}

Ecco fatto, è tutto pronto e puoi r̶i̶l̶a̶s̶s̶a̶r̶t̶i̶ iniziare a programmare! 🎉

Recupero dell'elenco dei prodotti nell'app

Sono tonnellate, le cose utili di react-native-adapty. Prima o poi ne avrai bisogno, ecco perché dovresti inizializzare la libreria all'inizio del flusso. Vai il più in alto possibile nel codice dell'app (è possibile farlo anche in App.tsx) e avvia l'inizializzazione:


// 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' });
  },[]);
  ...
}

Qui, sostituisci MY_PUBLIC_KEY con la chiave SDK pubblica presente nelle impostazioni della dashboard. In realtà, il metodo activateAdapty() può essere richiamato più volte e in più punti, ma noi ci atterremo a questa struttura.

Ora possiamo recuperare i prodotti aggiunti nella dashboard di Adapty:


import { adapty } from 'react-native-adapty';

async function getProducts() {
	const {paywalls, products} = await adapty.paywalls.getPaywalls();

	return products;
}

Ora passiamo all’esercizio: Cercheremo di costruire una piccola app in cui sia possibile sfogliare i prodotti dei nostri paywall ed effettuare acquisti.

Start for free

Convenient in-app purchases infrastructure.

Adapty SDK has it all:
— server-side purchase validation,
— all side cases covered,
— simple implementation.

Start for free

App di esempio

D'ora in poi sarò breve, per evitare di complicare eccessivamente la logica di base. Inoltre, programmerò in TypeScript per mostrarti quali tipi vengono utilizzati e dove. Per i test, utilizzerò il mio buon vecchio iPhone 8. Ricorda che a partire da iOS 14, App Store vieta l'uso degli store kit negli emulatori: puoi testare solo con dispositivi fisici.

Componente principale App.tsx

1. Per prima cosa, creiamo un componente principale App.tsx che avrà un pulsante di visualizzazione del paywall. Abbiamo già configurato la navigazione tramite react-native-navigation: riteniamo che sia molto meglio dell'opzione react-navigation raccomandata nella documentazione ufficiale.

PROBLEMA


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([]);

  useEffect(() => {
    async function fetchPaywalls(): Promise {
      await activateAdapty({ sdkKey: "MY_PUBLIC_KEY" });

      const result = await adapty.paywalls.getPaywalls();
      setPaywalls(result.paywalls);
    }

    fetchPaywalls();
  }, []);

  return (
    
      <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...
        }}
      />
    
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
});

Cosa succede? Al montaggio, viene invocata la funzione fetchPaywalls(). Attiva l'SDK e salva i paywall in questo stato, in modo che l'utente non debba attendere il recupero dopo aver toccato il pulsante. C'è solo un pulsante nella schermata che dovrebbe portare l'utente al paywall che abbiamo progettato in precedenza nella dashboard.

In realtà, è possibile recuperare i paywall proprio qui, senza salvarli in questo stato. Per impostazione predefinita, adapty.paywalls.getPaywalls() li recupera dalla memoria cache (dopo averli memorizzati nella cache al momento dell'avvio), il che significa che non dovrai attendere che il metodo parli con il server.

Ecco il risultato:

react native in-app purchases tutorial payall

Componente paywall

2. Scriviamo un componente paywall nello stesso file.


// 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;
}
export const Paywall: React.FC = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState(false);

  return (
    
      {paywall.products.map((product) => (
        
          {product.localizedTitle}
          <Button
            title={`Buy for за ${product.localizedPrice}`}
            disabled={isLoading}
            onPress={async () => {
              try {
                setIsLoading(true);
                await onRequestBuy(product);
              } catch (error) {
                alert("Error occured :(");
              } finally {
                setIsLoading(false);
              }
            }}
          />
        
      ))}
    
  );
};

// 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"),
  },
});

In questo caso, ci limiteremo a mappare i prodotti dal paywall e a visualizzare un pulsante di acquisto accanto a ciascun prodotto.

Registrazione della schermata

3. Per vedere come appare, registriamo questa schermata in react-native-navigation. Se utilizzi un'altra navigazione, salta questo passaggio. Il mio file index.js principale viene visualizzato in questo modo:


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" } }] } },
  });
});

Pulsante "Mostra il paywall”

4. Ora dobbiamo solo assegnare un'azione al pulsante "Mostra il paywall". Nel nostro caso, verrà richiesto un messaggio modale tramite la navigazione.


Navigation.showModal({
    component: {
      name: "Paywall",
      passProps: {
        paywall,
        onRequestBuy: async (product) => {
          const purchase = await adapty.purchases.makePurchase(product);
          // Doing everything we need
          console.log("purchase", purchase);
        },
      },
    },
  });

Il file App.tsx completo:


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([]);
  useEffect(() => {
    async function fetchPaywalls(): Promise {
      await activateAdapty({
        sdkKey: "MY_PUBLIC_KEY",
      });

      const result = await adapty.paywalls.getPaywalls();
      setPaywalls(result.paywalls);
    }

    fetchPaywalls();
  }, []);

  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({
            component: {
              name: "Paywall",
              passProps: {
                paywall,
                onRequestBuy: async (product) => {
                  const purchase = await adapty.purchases.makePurchase(product);
                  // Doing everything we need
                  console.log("purchase", purchase);
                },
              },
            },
          });
        }}
      />
    
  );
};

interface PaywallProps {
  paywall: AdaptyPaywall;
  onRequestBuy: (product: AdaptyProduct) => void | Promise;
}
export const Paywall: React.FC = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState(false);

  return (
    
      {paywall.products.map((product) => (
        
          {product.localizedTitle}
          <Button
            title={`Buy for ${product.localizedPrice}`}
            disabled={isLoading}
            onPress={async () => {
              try {
                setIsLoading(true);
                await onRequestBuy(product);
              } catch (error) {
                alert("Error occured :(");
              } finally {
                setIsLoading(false);
              }
            }}
          />
        
      ))}
    
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
  paywallContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-evenly",
    backgroundColor: PlatformColor("secondarySystemBackground"),
  },
});

È tutto! Ora puoi mostrare questi paywall ai tuoi utenti.

Se desideri testare l'abbonamento a iOS in un sandbox, dovrai creare il tuo account tester sandbox. Tieni presente che gli abbonamenti nel sandbox vengono rapidamente invalidati per facilitare i test. Per Android, non è necessario alcun account aggiuntivo: è possibile eseguire i test anche in un emulatore.

Verifica dell'eventuale presenza di abbonamenti attivi per l’utente

Dobbiamo ancora decidere dove memorizzare i dati dell'abbonamento attivo per garantire all'utente finale l'accesso ai contenuti premium. Adapty ci aiuterà anche in questo, poiché salva tutti gli acquisti associati all'utente. Facciamo così: se l'utente non ha un abbonamento, gli verrà mostrato un pulsante per il paywall. Se ce l’ha, gli mostreremo la foto di un gatto. 

Poiché i dati dell'abbonamento attivo vengono recuperati dal server o dalla memoria cache, è necessario un loader. Per semplicità, aggiungiamo gli stati isLoading e 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();
  }, []);

  // ...
}
// ...

Ecco cosa è cambiato: abbiamo aggiunto due flag allo stato. L'intero contenuto di fetchPaywalls() è ora racchiuso in un blocco try-catch, in modo che il codice raggiunga setIsLoading(false) in ogni possibile scenario. Per verificare se l'utente abbia un abbonamento attivo, recuperiamo il profilo dell'utente (che contiene tutti i suoi dati di abbonamento) e vediamo il valore di profile.accessLevels.premium.isActive. È possibile utilizzare tutti i livelli di accesso  (accessLevels) che, in pratica, sono solo livelli di abbonamento, come Gold o Premium, che si desidera, ma per ora manteniamo il valore predefinito. Adapty creerà automaticamente il livello di accesso premium e questo, per la maggior parte delle app, sarà sufficiente. isActive resterà true quando c'è un abbonamento attivo con questo livello di accesso

Da qui in poi, tutto è abbastanza semplice. Se l'utente ha un abbonamento di livello premium, non è necessario recuperare i paywall: basterà disabilitare il loader e visualizzare il contenuto.


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>;
};

In questo caso, aggiungiamo una funzione che rende il contenuto e una logica a onRequestBuy: in particolare, l'aggiornamento dello stato di isPremium e la chiusura della finestra modale.

Questo è il risultato finale:

Il file completo:


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(true);
  const [isPremium, setIsPremium] = useState(false);
  const [paywalls, setPaywalls] = useState([]);

  useEffect(() => {
    async function fetchPaywalls(): Promise {
      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 Loading...;
    }

    if (isPremium) {
      return (
        
      );
    }

    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({
            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 {renderContent()};
};
interface PaywallProps {
  paywall: AdaptyPaywall;
  onRequestBuy: (product: AdaptyProduct) => void | Promise;
}
export const Paywall: React.FC = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState(false);

  return (
    
      {paywall.products.map((product) => (
        
          {product.localizedTitle}
          <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);
              }
            }}
          />
        
      ))}
    
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
  paywallContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-evenly",
    backgroundColor: PlatformColor("secondarySystemBackground"),
  },
});

Per ricapitolare

Abbiamo finito per costruire un'app di abbonamento bella da vedere ed estremamente utile. Chi paga vedrà i gatti, tutti gli altri avranno i paywall. Questa guida dovrebbe averti insegnato tutto ciò che ti serve per implementare gli acquisti in-app nella tua applicazione. Se desideri approfondire gli store kit, resta sintonizzato per saperne di più. Grazie!

Further reading

Ultimate guide on Apple Subscription Offers. Part 1
Ultimate guide on Apple Subscription Offers. Part 1
February 23, 2021
7 min read
Tutorial: how to implement in-app purchases into a flutter app
Tutorial: how to implement in-app purchases into a flutter app
January 10, 2022
12 min read
Adapty April Updates: all new cohorts, SearchAdsHQ integration and hiring new team members!
Adapty April Updates: all new cohorts, SearchAdsHQ integration and hiring new team members!
May 5, 2022
3 min read