Pular para o conteúdo

Apps white-label mais inteligentes com SDKs modulares

white-label apps with modular sdks | | Cheesecake Labs
Resumo
  • O artigo mostra como criar uma arquitetura modular em apps white-label cross-platform (React Native ou Flutter) que permite ligar ou desligar SDKs por cliente, mantendo o app leve, estável e fácil de manter.
  • O gerenciamento de dependencies em tempo de build pode ser feito com um shell script que lê variáveis de ambiente (como ANALYTICS_ENABLED e DEEPLINK_ENABLED) e roda yarn add ou yarn remove, instalando apenas os SDKs necessários para cada build.
  • No React Native, o Expo Prebuild e os Expo Config Plugins permitem gerar código nativo apenas com os plugins de SDK necessários, condicionando a inclusão a variáveis de ambiente; para imports dinâmicos, o script pode gerar um arquivo de mapeamento dos SDKs para evitar quebras quando uma dependency estiver ausente.
  • Uma camada de abstração unificada, com um adapter abstrato e uma classe global (por exemplo, Analytics), faz o app conversar com uma interface única; no Flutter ou React Native puro, sem equivalente ao Expo Prebuild, alterações nativas exigem scripts de patch, módulos wrapper, manifest merging ou ferramentas como XcodeGen.

Se você desenvolve um app white-label, provavelmente já passou por isso: um cliente pede um SDK (Software Development Kit) específico para analytics, deep links ou outras features, e você fica se perguntando como oferecer várias opções de SDK para clientes diferentes sem aumentar o tamanho do app nem comprometer a estabilidade. 

Neste artigo, você vai ver como resolver esse problema em projetos cross-platform. Seja com React Native ou Flutter, o objetivo é o mesmo: criar uma arquitetura modular que permita ligar ou desligar SDKs por cliente, mantendo o app white-label leve, estável e fácil de manter.

Os desafios a resolver

Ao montar uma solução assim em um app white-label cross-platform, alguns desafios precisam ser enfrentados:

  1. Gerenciar dependencies em tempo de build: instalar, remover e configurar cada uma conforme o cliente.
  2. Garantir que o código nativo não usado fique totalmente de fora, para manter o app leve e evitar complexidade desnecessária.
  3. Lidar com imports dinâmicos de dependencies, para ligar e desligar features sem quebrar o build nem aumentar o acoplamento.
  4. Oferecer uma camada de abstração unificada, para que o app converse com analytics, deep links e outras features sem chamar cada SDK individualmente.

Leia também: Como criar um app React Native usando TypeScript

Gerenciar dependencies em tempo de build

Uma forma eficaz de resolver o gerenciamento de dependencies em tempo de build é usar um shell script que instala ou remove SDKs dinamicamente, com base em variáveis de ambiente. Antes de o build começar, o script pode ler flags como ANALYTICS_ENABLED, DEEPLINK_ENABLED e qualquer configuração específica do cliente, e então rodar os comandos yarn add ou yarn remove correspondentes.

Assim, só os SDKs necessários entram no projeto naquele build específico.

Garantir que o código nativo não usado fique de fora

No ecossistema React Native, esse desafio fica bem mais simples graças ao Expo Prebuild e aos Expo Config Plugins. Com eles, você gera um código nativo que inclui apenas os plugins de SDK necessários para aquele build.

Veja um exemplo com o SDK da Klaviyo. Quando o expo prebuild roda, ele gera os projetos nativos de iOS e Android automaticamente, já com toda a configuração da Klaviyo, então você não precisa escrever nem manter código nativo.

Ao envolver a definição do plugin em uma condição que depende do ambiente (process.env.KLAVIYO_ENABLED), o SDK só entra quando aquele build precisa dele. Com isso, o app de cada cliente carrega apenas as integrações nativas de que precisa, sem código extra nem dependencies desnecessárias.

// app.config.js
{
  expo: {
    name: 'My app',
    ios: {...},
    android: {...},
    plugins: [
      process.env.KLAVIYO_ENABLED && [
        'klaviyo-expo-plugin',
        {
          version: process.env.APP_VERSION,
          ios: {
            buildNumber: process.env.APP_BUILD_NUMBER,
            bundleIdentifier: process.env.BUNDLE_ID,
            infoPlist: {
              UIBackgroundModes: ['remote-notification'],
            },
            badgeAutoclearing: true,
            codeSigningStyle: 'Manual',
            devTeam: process.env.APP_TEAM_ID,
          },
          android: {
            package: process.env.PACKAGE_NAME,
            openTracking: true,
            logLevel: 1,
            notificationIconFilePath: './assets/images/icon.png',
          },
        },
      ]
    ],
  }
}Code language: JavaScript (javascript)

Se um SDK não oferece um plugin oficial do Expo, você ainda consegue aproveitar esse workflow criando seu próprio plugin. Veja esta documentação sobre mods dos Expo Config Plugins.

Lidar com imports dinâmicos de dependencies

Quando você remove uma dependency em tempo de build, qualquer import direto daquele pacote nos seus arquivos falha, já que a biblioteca não está mais instalada. Isso pode quebrar o build ou gerar erros em runtime se não for tratado.

Uma saída é dar ao seu shell script a capacidade de gerar também um arquivo de mapeamento dinâmico dos SDKs. Por exemplo:

// No SDK installed
const sdks = {
  firebase: undefined as any,
  klaviyo: undefined as any
}

export const firebase = sdks.firebase
export const klaviyo = sdks.klaviyo

export default sdksCode language: JavaScript (javascript)
// With the SDKs installed
import { Klaviyo } from 'klaviyo-react-native-sdk'
import { getAnalytics, logEvent } from '@react-native-firebase/analytics'

const sdks = {
  firebase: { getAnalytics, logEvent },
  klaviyo: Klaviyo
}

export const firebase = sdks.firebase
export const klaviyo = sdks.klaviyo

export default sdksCode language: JavaScript (javascript)


Depois, use esse arquivo para tratar os imports dos SDKs com segurança, garantindo que seu código nunca quebre quando uma dependency estiver ausente.

Oferecer uma camada de abstração unificada

Uma camada de abstração unificada mantém seu código limpo e evita que você chame cada SDK diretamente. Se você tem vários SDKs de analytics, por exemplo, dá para criar um adapter abstrato de analytics e uma classe global de analytics que concentra todos os métodos necessários.

A classe principal Analytics decide quais SDKs carregar com base na configuração, enquanto cada SDK (como o da Klaviyo) apenas implementa o contrato compartilhado.

abstract class AnalyticsAdapter {
  abstract logEvent(event: string, payload: Record<string, any>): void
  abstract initialize(config: any): Promise<void>
}

enum AnalyticsAdapterType {
  DEFAULT = 'default',
  KLAVIYO = 'klaviyo',
  ...
}

class Analytics extends AnalyticsAdapter {
  type: AnalyticsAdapterType = AnalyticsAdapterType.DEFAULT
  private adapters: AnalyticsAdapter[] = []

  logEvent(event: string, payload: Record<string, any>): void {
    this.adapters.forEach(adapter => adapter.logEvent(event, payload))
  }Code language: PHP (php)
async initialize(config: any): Promise<void> {
    // create adapter instances
    if (config.klaviyo.enabled && process.env.KLAVIYO_ENABLED) {
      this.adapters.push(new KlaviyoAnalytics())
    }

    // initialize all adapters
    this.adapters.forEach(adapter =>      
adapter.initialize(config[adapter.type]))
  }
}Code language: JavaScript (javascript)
import Klaviyo from 'sdks'

class KlaviyoAnalytics extends AnalyticsAdapter {
  type: AnalyticsAdapterType = AnalyticsAdapterType.KLAVIYO
  logEvent(event: string, payload: Record<string, any>): void {
    // ... Klaviyo SDK methods
  }
  async initialize(config: any): Promise<void> {
    Klaviyo.initialize(config.key)
  }
}Code language: JavaScript (javascript)

Assim, o resto do seu app white-label conversa com uma interface única.

Como fazer isso no Flutter ou no React Native puro?

Se o seu SDK não exige código nativo customizado, está tudo certo. Basta abstrair os imports e a lógica dos adapters.

Agora, se você precisa mesmo mexer em arquivos nativos, a coisa fica mais complexa. Nesses casos, dá para criar scripts que aplicam patch nos arquivos de Android e iOS, montar um módulo wrapper para encapsular a lógica nativa, apoiar-se em mecanismos como o manifest merging do Android ou até tentar ferramentas como o XcodeGen.

Infelizmente, não existe nada equivalente ao Expo Prebuild para simplificar esse workflow no Flutter ou no React Native puro.

Leia também: Flutter, React Native ou desenvolvimento nativo: o que você precisa saber

FAQ

Qual problema o artigo busca resolver em apps white-label cross-platform?

Como oferecer várias opções de SDK para clientes diferentes sem aumentar o tamanho do app nem comprometer a estabilidade, criando uma arquitetura modular que permita ligar ou desligar SDKs por cliente, mantendo o app leve, estável e fácil de manter.

Como gerenciar as dependencies dos SDKs em tempo de build?

Usando um shell script que instala ou remove SDKs dinamicamente com base em variáveis de ambiente. Antes do build, o script lê flags como ANALYTICS_ENABLED e DEEPLINK_ENABLED e roda os comandos yarn add ou yarn remove correspondentes, para que só os SDKs necessários entrem naquele build.

Como garantir que o código nativo não usado fique fora do app em React Native?

Com o Expo Prebuild e os Expo Config Plugins, que geram código nativo apenas com os plugins de SDK necessários. Envolvendo a definição do plugin em uma condição de ambiente (como process.env.KLAVIYO_ENABLED), o SDK só entra quando o build precisa dele. Se o SDK não tiver plugin oficial do Expo, é possível criar um plugin próprio.

Como evitar que imports de SDKs removidos quebrem o build?

Fazendo o shell script gerar também um arquivo de mapeamento dinâmico dos SDKs: quando nenhum SDK está instalado, as exportações ficam undefined; quando estão instalados, o arquivo importa e exporta os módulos reais. Esse arquivo é usado para tratar os imports com segurança.

É possível aplicar essa abordagem em Flutter ou React Native puro?

Se o SDK não exige código nativo customizado, basta abstrair os imports e a lógica dos adapters. Se for preciso mexer em arquivos nativos, é possível criar scripts de patch para Android e iOS, montar um módulo wrapper, usar o manifest merging do Android ou ferramentas como o XcodeGen, mas não existe equivalente ao Expo Prebuild nesses ambientes.