DEV Community

Cover image for Toggle Features - Ativando e Desativando recursos em Produção
Toshi Ossada for flutterbrasil

Posted on • Edited on

Toggle Features - Ativando e Desativando recursos em Produção

Fala Devs blz?

Em certos momentos possa ser que precisemos ligar ou desligar algum recurso de nosso aplicativo já em produção. Uma das opções possíveis para desativar um recurso já em produção é fazer o rollback da aplicação, entretanto isso pode ser um pouco doloroso, pois fazer a publicação do aplicativo nas lojas é um pouco burocrático e demora algum tempo.

Uma outra opção possível é utilizar o conceito de Features Toggle, também conhecido como Features Flag. É uma técnica que te permite ativar ou desativar recursos ou funcionalidades do seu aplicativo em produção sem a necessidade de ter que fazer a publicação do seu aplicativo novamente, sendo possível até mesmo em tempo real. Podemos utilizar por diversos motivos como: desativar um recurso que foi encontrado um bug em produção e reativar quando for corrigido, liberação gradativa de alguma funcionalidade nova, teste A/B, etc.

Agora irei demonstrar uma implementação que fiz demonstrando a ideia do features toggle.

O aplicativo que iremos utilizar de exemplo é o abaixo

Iremos possibilitar a ativação ou desativação dos botões dessa tela de maneira remota.

Primeiramente vamos definir o modelo de retorno externo de como será definido se a feature estará habilitada ou desabilitada

{
"data": [
{"id": "user.btnLoginFacebook", "enabled": true},
{"id": "user.btnLoginApple", "enabled": true},
{"id": "user.btnLoginGoogle", "enabled": true},
{"id": "tecnology.label", "enabled": true},
{"id": "tecnology.botao1", "enabled": true},
{"id": "tecnology.botao2", "enabled": true},
{"id": "tecnology.botao3", "enabled": true},
{"id": "business.label", "enabled": true},
{"id": "business.botao1", "enabled": true},
{"id": "business.botao2", "enabled": true},
{"id": "business.botao3", "enabled": true}
]
}

Note que é uma lista de Objetos que tem o id, a feature, e se ela está habilitada ou desabilitada, então a entidade do nosso recurso ficará:
class FeatureToggleEntity {
final String id;
final bool enabled;
const FeatureToggleEntity({
required this.id,
required this.enabled,
});
}

e nossa model ficaria semelhante a entidade só que com métodos fromJson e toJson

A partir daí conseguimos construir nosso Datasource fazendo à requisição a nossa API das configurações dos recursos.

class FeatureToggleModel extends FeatureToggleEntity {
FeatureToggleModel({
required super.id,
required super.enabled,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'enabled': enabled,
};
}
factory FeatureToggleModel.fromMap(Map<String, dynamic> map) {
return FeatureToggleModel(
id: map['id'] ?? '',
enabled: map['enabled'] ?? false,
);
}
String toJson() => json.encode(toMap());
factory FeatureToggleModel.fromJson(String source) =>
FeatureToggleModel.fromMap(json.decode(source));
}

Agora precisamos criar um Store global que irá armazenar esses valores em memória e é necessário que fique de forma global, no caso utilizei o ValueNotifier, mas utilize qualquer gerenciador de estado de sua preferência.

class ToggleFeatureStore extends ValueNotifier<List<FeatureToggleEntity>> {
ToggleFeatureStore() : super([]);
}

Daí precisaremos carregar esse valor no momento de inicialização do aplicativo, uma forma que gosto de fazer para garantir isso é construindo uma SplashScreen na rota principal do aplicativo e quando finalizar o carregamento ir para a página principal do aplicativo.
class SplashController {
final LoadFeaturesToggle loadFeaturesToggle;
final ToggleFeatureStore toggleFeatureStore;
SplashController({
required this.loadFeaturesToggle,
required this.toggleFeatureStore,
});
init() async {
toggleFeatureStore.value = await loadFeaturesToggle();
await Future.delayed(const Duration(seconds: 2));
Modular.to.navigate('/home/business/');
}
}

Após isso vamos criar um widget que receberá um Widget como parâmetro e o id da feature, com isso ele vai englobar o widget em um Visibility() mostrando ou escondendo o widget conforme a lista que está armazenada na nossa store.
class ToggleFeatureWidget extends StatelessWidget {
final String toggleFeatureId;
final Widget child;
bool visible(BuildContext context) {
final controller = context.getDep<ToggleFeatureController>();
return controller.enabled(toggleFeatureId);
}
const ToggleFeatureWidget({
super.key,
required this.toggleFeatureId,
required this.child,
});
@override
Widget build(BuildContext context) {
return Visibility(
visible: visible(context),
child: child,
);
}
}
class ToggleFeatureController {
final LoadFeaturesToggle loadFeaturesToggle;
final ToggleFeatureStore store;
ToggleFeatureController({
required this.loadFeaturesToggle,
required this.store,
});
bool enabled(String key) {
if (store.value.any((element) => element.id == key)) {
return store.value.firstWhere((element) => element.id == key).enabled;
}
return false;
}
}

Desta forma na página do aplicativo podemos utilizar esse widget que criamos e este irá envolver os recursos que queremos aplicar o Feature Toggle.

class BusinessPage extends StatefulWidget {
const BusinessPage({super.key});
@override
State<BusinessPage> createState() => _BusinessPageState();
}
class _BusinessPageState extends State<BusinessPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Business'),
),
body: Column(
children: [
const ToggleFeatureWidget(
toggleFeatureId: 'business.label',
child: Text('Business'),
),
ToggleFeatureWidget(
toggleFeatureId: 'business.botao1',
child:
ElevatedButton(onPressed: () {}, child: const Text('Botao 1')),
),
ToggleFeatureWidget(
toggleFeatureId: 'business.botao2',
child:
ElevatedButton(onPressed: () {}, child: const Text('Botao 2')),
),
ToggleFeatureWidget(
toggleFeatureId: 'business.botao3',
child:
ElevatedButton(onPressed: () {}, child: const Text('Botao 3')),
),
],
),
);
}
}

Desta forma quando um recurso é desabilitado remotamente

Irá refletir no aplicativo de forma quase que instantânea

Legal né? Lembrando que poderíamos ter usado este recurso com um Socket, como o Firebase para que a ativação e desativação seja de forma instantânea, caso seu negócio necessite

Confira o exemplo completo em
https://github.com/toshiossada/togglefeature

Image description

Entre em nosso discord para interagir com a comunidade: https://discord.com/invite/flutterbrasil
https://linktr.ee/flutterbrasil

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs