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

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay