Vocabulário do Kotlin: Delegates
Uma das formas de executar um trabalho é delegar esse trabalho a outra parte. Não estou falando sobre delegar seu trabalho para um amigo, mas sim de delegar trabalho de um objeto para outro.
E a delegação não é uma novidade na área do software. A delegação é um padrão de design no qual um objeto lida com uma solicitação delegando-a a um objeto auxiliar, conhecido como delegate. O delegate é responsável por lidar com a solicitação em nome do objeto original e por disponibilizar os resultados ao objeto original.
O Kotlin facilita a delegação fornecendo suporte a delegates de classe e propriedade e até mesmo contendo alguns delegates integrados próprios.
Digamos que você tenha um caso de uso para um ArrayList que possa recuperar seu último item removido. Basicamente, tudo o que você precisa é da mesma funcionalidade ArrayList com uma referência ao último item removido.
Uma forma de fazer isso é estender a classe ArrayList. Como essa nova classe está estendendo o ArrayList concreto, não implementando a interface MutableList, ela está altamente vinculada à implementação do ArrayList concreto.
Não seria legal poder substituir a função remove() para manter uma referência do item excluído e delegar o restante das implementações vazias de MutableList para outro objeto? O Kotlin oferece uma forma de fazer isso delegando a maior parte do trabalho para uma instância interna de ArrayList e personalizando seu comportamento. Para isso, o Kotlin introduz uma nova palavra-chave: by.
Vejamos como funciona a delegação de classes. Quando você usa a palavra-chave by, o Kotlin automaticamente gera o código para usar a instância de innerList como um delegate.
A palavra-chave by instrui o Kotlin a delegar a funcionalidade da interface MutableList para uma instância interna de ArrayList, chamada innerList. O ListWithTrash ainda dá suporte a todas as funções da interface MutableList fornecendo métodos de ligação direta com o objeto ArrayList interno. E, agora, você tem a capacidade de adicionar seu próprio comportamento.
Vamos ver como isso funciona. Se você analisar o código Java descompilado do código de byte ListWithTrash, verá que o compilador do Kotlin na verdade cria funções wrapper que chamam as funções correspondentes do objeto ArrayList interno.
Observação: o compilador do Kotlin usa outro padrão de design chamado Padrão decorator, para dar suporte à delegação de classes no código gerado. No padrão decorator, a classe decorator compartilha a mesma interface com a classe a ser decorada. A classe decorator mantém uma referência interna da classe de destino e agrupa, ou decora, todos os métodos públicos fornecidos com a interface.
Os delegates são especialmente úteis quando não é possível herdar de uma determinada classe. Com a delegação de classes, a classe não faz parte de nenhuma hierarquia. Em vez disso, ela compartilha a mesma interface e decora o objeto interno do tipo original. Isso significa facilidade para trocar a implementação sem danificar a API pública.
Além da delegação de classes, também é possível usar a palavra-chave by para delegar propriedades. Com a delegação de propriedades, o delegate é responsável por lidar com as chamadas às funções get e set da propriedade. Isso pode ser extremamente útil se for preciso reutilizar a lógica getter/setter em outros objetos, além de permitir estender facilmente a funcionalidade além dos campos auxiliares simples.
Vamos supor que você tenha uma classe Person definida da seguinte maneira:
class Person(var name: String, var lastname: String)
A propriedade name dessa classe tem alguns requisitos de formatação. Quando name é definida, convém garantir que a primeira letra seja capitalizada, deixando o restante em letras minúsculas. Além disso, na atualização de name, convém incrementar automaticamente a propriedade update count.
Você poderia implementar essa funcionalidade da seguinte maneira:
Embora isso funcione, e se os requisitos mudarem e você também quiser incrementar updateCount sempre que lastname mudar? Você poderia copiar/colar a lógica para escrever um setter personalizado, mas acabaria escrevendo exatamente o mesmo setter para cada propriedade:
Os dois métodos setter são quase idênticos, o que é uma indicação de que um deles não deveria existir. Com a delegação de propriedades, é possível reutilizar o código delegando getters e setters a uma propriedade.
Assim como na delegação de classes, você pode usar a palavra-chave by para delegar uma propriedade, e o Kotlin gerará o código para usar o delegate quando a sintaxe property for utilizada.
Com essa mudança, você delegou as propriedades name e lastname à classe FormatDelegate. Agora, vamos analisar o código para FormatDelegate. A classe delegate precisa implementar ReadProperty<Any?, String> se você precisar delegar apenas o getter, ou ReadWriteProperty<Any?, String> se você precisar delegar o getter e o setter. Em nosso caso, FormatDelegate precisa implementar ReadWriteProperty<Any?, String>, uma vez que queremos fazer a formatação quando o setter for invocado.
Você deve ter notado que há dois parâmetros adicionais nas funções getter e setter. O primeiro é thisRef, e ele representa o objeto que contém a propriedade. thisRef pode ser usado para acessar o próprio objeto para fins como verificar outras propriedades ou chamar outras funções de classe. O segundo parâmetro é KProperty<*>, que pode ser usado para acessar metadados na propriedade delegada.
Voltando ao requisito, vamos usar thisRef para acessar e incrementar a propriedade updateCount.
Para entender como isso funciona, vamos analisar o código Java descompilado. O compilador do Kotlin gera o código para manter referências privadas do objeto FormatDelegate para as propriedades name e lastname, juntamente com getters/setters que contêm a lógica adicionada.
O compilador também cria uma KProperty[] para armazenar as propriedades delegadas. Se você analisar os getters e setters gerados para a propriedade name, a instância é armazenada no índice 0, ao passo que a propriedade lastname é armazenada no índice 1.
Com esse truque, qualquer autor da chamada pode acessar a propriedade delegada com a sintaxe regular da propriedade.
person.lastname = “Smith” // calls generated setter, increments count
println(“Update count is $person.count”)
O Kotlin não apenas dá suporte aos delegates, como também fornece delegates integrados na biblioteca padrão do Kotlin, algo que veremos em mais detalhes em outro artigo.
Os delegates podem ajudar a delegar tarefas para outros objetos e a reutilizar melhor o código. O compilador do Kotlin cria o código para permitir o uso otimizado dos delegates. O Kotlin usa uma sintaxe simples com a palavra-chave by para delegar uma propriedade ou classe. Nos bastidores, o compilador do Kotlin gera todo o código necessário para dar suporte à delegação sem expor nenhuma mudança à API pública. Basicamente, o Kotlin gera e mantém todo o código boilerplate necessário para os delegates. Ou, em outras palavras, é possível delegar delegates para o Kotlin.
Vocabulário do Kotlin
Breves e fáceis de usar, os argumentos padrão permitem implementar sobrecargas de funções sem o boilerplate. Assim como muitos recursos do Kotlin, isso pode parecer mágico. Curioso para conhecer os segredos? Continue lendo e descubra como os argumentos padrão do Kotlin funcionam nos bastidores.
Se você precisar sobrecarregar uma função, em vez de implementar a mesma função várias vezes, você pode usar argumentos padrão:
Os argumentos padrão também podem ser aplicados a construtores:
Normalmente, o Java não reconhece a sobrecarga de valores padrão:
Para instruir o compilador a gerar os métodos de sobrecarga, use a anotação @JvmOverloads na função do Kotlin:
Vamos analisar o código Java descompilado para ver o que o compilador gera para nós: Tools -> Kotlin -> Show Kotlin Bytecode e, em seguida, pressione o botão Decompile.
Vemos que o compilador gera duas funções:
O valor do parâmetro int de play$default é computado com base no número e no índice dos argumentos que recebem um argumento padrão. Com base no valor desse parâmetro, o compilador do Kotlin sabe com quais parâmetros chamar a função play.
Em nossa chamada play() de exemplo, o argumento no índice 0 está usando o argumento padrão. Portanto, play$default é chamado com int var1 = 2⁰:
play$default((Toy)null, 1, (Object)null);
Assim, implementação de play$default sabe que o valor de var0 deve ser substituído pelo valor padrão.
Vejamos um exemplo mais complexo do comportamento do parâmetro int. Vamos expandir nossa função play e, no local da chamada, usar o argumento padrão para doggo e toy:
Vejamos o que aconteceu no código descompilado:
Agora, vemos que o parâmetro int é 5. Isso foi calculado assim: os parâmetros nas posições 0 e 2 estão usando o argumento padrão, portanto, var3 = 2⁰ + 2² = 5. Em uma operação bitwise AND, os parâmetros são avaliados assim:
Com base no bitmask aplicado a var3, o compilador pode calcular quais parâmetros devem ser substituídos por valores padrão.
Nos exemplos acima, você deve ter notado que o valor do parâmetro Object é sempre nulo e que, na verdade, nunca é usado na função play$default. Esse parâmetro é conectado aos valores padrão de suporte nas funções substitutas.
O que acontece quando queremos substituir uma função por argumentos padrão?
Vamos mudar o exemplo acima e:
Quando tentamos definir um valor padrão em PlayfulDoggo.play, vemos que isso não é permitido: uma função substituta não tem permissão para especificar valores padrão para seus parâmetros
Se removermos a substituição e verificarmos o código descompilado, PlayfulDoggo.play() se parecerá com o seguinte:
Isso significa que as superchamadas com argumentos padrão terão suporte futuramente? Teremos que esperar para ver.
Para os construtores, o código Java descompilado tem apenas uma diferença. Vamos dar uma olhada:
Os construtores também criam um método sintético, mas, em vez do Object usado nas funções, os construtores usam um DefaultConstructorMarker, que é chamado com nulo:
Os construtores secundários com argumentos padrão também gerarão outro método sintético utilizando um DefaultConstructorMarker, como os construtores primários:
Simples e úteis, os argumentos padrão reduzem a quantidade de código boilerplate a ser escrito para lidar com métodos sobrecarregados, permitindo definir valores padrão para os parâmetros. Assim como em muitas das palavras-chave do Kotlin, é possível compreender a mágica analisando o código que é escrito para nós. Confira nossas outras postagens sobre Vocabulário do Kotlin para saber mais.
O conjunto de tecnologias do FlutterFire, composto pelo Flutter e pelo Firebase (e, especificamente pelo Cloud Firestore), desbloqueia uma velocidade de desenvolvimento sem precedentes durante a criação e o lançamento de aplicativos. Neste artigo, vamos explorar a integração robusta dessas duas tecnologias, com foco nos testes e no uso de padrões arquitetônicos limpos. No entanto, em vez de passar direto para a implementação final, vamos percorrer o caminho, passo a passo, para que a lógica por trás de cada etapa fique clara.
Para demonstrar uma forma limpa de implementar o Cloud Firestore como o back-end do aplicativo, vamos criar uma versão modificada do aplicativo contador clássico do Flutter. A única diferença é que o carimbo de data/hora de cada clique é armazenado no Cloud Firestore, e a contagem exibida é derivada do número de carimbos de data/hora mantidos. Você usará o Provider e o ChangeNotifier para manter o código de dependências e gerenciamento de estados limpo, e atualizará o teste gerado para manter o código correto.
Este artigo supõe que você já assistiu e seguiu as etapas deste tutorial para integrar o aplicativo ao Firebase. Resumo:
Observação: se você configurar o aplicativo para funcionar em um cliente Android, terá que criar um arquivo debug.keystore antes de gerar o certificado SHA1.
Após a geração dos aplicativos iOS ou Android no Firebase, você estará pronto para continuar. O restante do vídeo traz um ótimo conteúdo, do qual você provavelmente precisará nos projetos reais, mas isso não é necessário para este tutorial.
Se qualquer uma das etapas deste tutorial não funcionar para você, consulte este repositório público, que detalha as mudanças em diferentes commits. Ao longo do tutorial, você encontrará links para cada commit, quando apropriado. Sinta-se à vontade para usar essa função a fim de verificar se está progredindo da forma esperada!
Para começar o processo de integração do aplicativo com o Cloud Firestore, você deve primeiro refatorar o código gerado, para que o StatefulWidget inicial se comunique com uma classe separada, em vez de com seus próprios atributos. Isso permite que você, eventualmente, instrua essa classe separada a usar o Cloud Firestore.
Ao lado do arquivo main.dart gerado automaticamente para o projeto, crie um novo arquivo chamado counter_manager.dart e copie para ele o seguinte código:
class CounterManager { /// Create a private integer to store the count. Make this private /// so that Widgets can't modify it directly, but instead must /// use official methods. int _count = 0;
/// Publicly accessible reference to our state. int get count => _count;
/// Publicly accessible state mutator. void increment() => _count++;}
Com esse código inserido, adicione a seguinte linha ao início do arquivo firebasecounter/lib/main.dart:
import 'package:firebasecounter/counter_manager.dart';
Depois, altere o código de _MyHomePageState para:
class _MyHomePageState extends State<MyHomePage> { final manager = CounterManager();
void _incrementCounter() { setState(() => manager.increment()); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( '${manager.count}', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }}
Depois de salvar a mudança do código, talvez pareça que o aplicativo falhou, pois ele mostrará uma tela de erro vermelha. Isso é porque você introduziu uma nova variável, manager, cuja oportunidade de inicialização já passou. Essa é uma experiência comum no Flutter, quando a forma como o estado é inicializadomuda, e a questão pode ser facilmente resolvida com uma reinicialização a quente.
Após a reinicialização a quente, você deverá voltar para o ponto inicial: na contagem 0 e capaz de clicar no botão de ação flutuante o quanto quiser.
Esse é um bom momento para executar o único teste que o Flutter fornece em qualquer novo projeto. A definição dele pode ser encontrada em test/widget_test.dart, e ele pode ser executado com o seguinte comando:
$ flutter test
Se o teste for bem-sucedido, você estará pronto para continuar!
Observação: caso você encontre obstáculos nesta seção, compare suas mudanças com este commit no repositório do tutorial.
A descrição inicial do aplicativo mencionava a manutenção do carimbo de data/hora de cada clique. Até agora, você não adicionou nenhuma infraestrutura para atender a esse segundo requisito, portanto, crie um novo arquivo chamado app_state.dart e adicione a seguinte classe:
/// Container for the entirety of the app's state. An instance of /// this class should be able to inform what is rendered at any/// point in time.class AppState { /// Full click history. For super important auditing purposes. /// The count of clicks becomes this list's `length` attribute. final List<DateTime> clicks;
/// Default generative constructor. Const-friendly, for optimal /// performance. const AppState([List<DateTime> clicks]) : clicks = clicks ?? const <DateTime>[]; /// Convenience helper. int get count => clicks.length;
/// Copy method that returns a new instance of AppState instead /// of mutating the existing copy. AppState copyWith(DateTime latestClick) => AppState([ latestClick, ...clicks, ]);}
A partir deste ponto, a função da classe AppState é representar o estado do que deve ser renderizado. A classe não contém nenhum método que possa sofrer mutações, apenas um único método copyWith, que será utilizado pelas outras classes.
Com os testes em mente, você pode começar a fazer mudanças no conceito do CounterManager. Ter uma única classe não será suficiente no longo prazo, porque o aplicativo eventualmente interage com o Cloud Firestore. Mas não é preciso criar registros reais todas as vezes que um teste é executado. Para isso, é necessária uma interface abstrata que defina como o aplicativo deve se comportar.
Abra o arquivo counter_manager.dart novamente e adicione o seguinte código no início do arquivo:
import 'package:firebasecounter/app_state.dart';
/// Interface that defines the functions required to manipulate/// the app state.////// Defined as an abstract class so that tests can operate on a /// version that does not communicate with Firebase.abstract class ICounterManager { /// Any `CounterManager` must have an instance of the state /// object. AppState state;
/// Handler for when a new click must be stored. Does not require /// any parameters, because it only causes the timestamp to /// persist. void increment();}
A próxima etapa é atualizar o CounterManager para descender explicitamente do ICounterManager. Atualize a definição dele da seguinte forma:
class CounterManager implements ICounterManager { AppState state = AppState();
void increment() => state = state.copyWith(DateTime.now());}
Neste ponto, nosso código auxiliar parece muito bom, mas o main.dart ficou para trás. Não há referência ao ICounterManager no main.dart, quando, na verdade, essa é a única classe Manager que ele deve conhecer. No main.dart, faça as seguintes mudanças:
2. Atualize _MyHomePageState da seguinte forma:
class _MyHomePageState extends State<MyHomePage> { final ICounterManager manager; _MyHomePageState({@required this.manager});
void _incrementCounter() => setState(() => manager.increment());
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( '${manager.state.count}', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }}
Essa mudança deve remover todas as linhas vermelhas sublinhadas do ambiente de desenvolvimento integrado de _MyHomePageState. Mas, agora, MyHomePage está reclamando, porque seu método createState() não fornece todos os argumentos necessários para _MyHomePageState. Você pode fazer com que MyHomePage solicite essa variável e transmita o objeto para sua classe baseada em estado, mas isso geraria longas cadeias de widgets solicitando e transmitindo objetos que não são realmente importantes para eles, simplesmente porque um widget descendente está exigindo e um widget ancestral está fornecendo. Claramente, isso requer uma estratégia melhor.
É aí que entra o Provider.
O Provider é uma biblioteca que otimiza o uso do padrão InheritedWidget do Flutter. Ele permite que um widget no topo da árvore de widgets seja acessível diretamente por todos os seus descendentes. Isso pode parecer similar a uma variável global, mas a alternativa é transmitir os modelos de dados por cada um dos widgets intermediários, quando muitos deles não terão nenhum interesse intrínseco por esses modelos. Esse antipadrão no estilo de “brigada de baldes de variáveis” ofusca a separação dos interesses do aplicativo e pode tornar os layouts de refatoração desnecessariamente enfadonhos. O InheritedWidget e o Provider ignoram esses problemas permitindo que os widgets em qualquer ponto da árvore obtenham diretamente os modelos de dados de que necessitam.
Para adicionar o Provider ao aplicativo, abra o pubspec.yaml e adicione-o abaixo da seção dependencies:
dependencies: flutter: sdk: flutter # Add this provider: ^4.3.2+2
Depois de adicionar essa linha ao arquivo pubspec.yaml, execute o seguinte para fazer o download do Provider para a máquina:
$ flutter pub get
Ao lado do main.dart, crie um novo arquivo chamado dependencies.dart e copie para ele o seguinte código:
import 'package:firebasecounter/counter_manager.dart';import 'package:flutter/material.dart';import 'package:provider/provider.dart';
class DependenciesProvider extends StatelessWidget { final Widget child; DependenciesProvider({@required this.child});
@override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<ICounterManager>(create: (context) => CounterManager()), ], child: child, ); }}
Algumas observações sobre o DependenciesProvider:
Em seguida, disponibilize o novo DependenciesProvider para todo o aplicativo. Uma forma simples de fazer isso é agrupar todo o widget MaterialApp com ele. Abra o main.dart e atualize o método principal da seguinte maneira:
void main() { runApp( DependenciesProvider(child: MyApp()), );}
Também é preciso importar o dependencies.dart para o main.dart:
import 'package:firebasecounter/dependencies.dart';
Já vimos o widget MultiProvider em ação (que é, na verdade, apenas uma forma mais bacana de declarar uma série de widgets Provider individuais). O próximo passo é acessar o objeto ICounterManager usando o widget Consumer.
Se você já escreveu um aplicativo Flutter usando o Cloud Firestore, provavelmente descobriu que o Firestore pode tornar mais complicado escrever bons testes de unidade. Afinal, como evitar gerar registros reais no banco de dados quando uma integração do Firestore é conectada diretamente à árvore de widgets?
Se você já teve essa experiência, já sabe das limitações para a incorporação de dependências diretamente no código da IU, que são os widgets, no caso do Flutter. Esse é o poder da injeção de dependências: se os widgets aceitarem classes auxiliares que facilitam sua interação com as dependências (como o Firebase, o sistema de arquivos do dispositivo ou mesmo as solicitações de rede), você pode fornecer itens fictícios ou falsos em vez das classes reais durante os testes. Com isso, é possível testar se os widgets se comportam como deveriam sem precisar esperar por solicitações lentas de rede, encher o sistema de arquivos ou incorrer em taxas de faturamento do Firebase.
Para conseguir isso, você precisa refatorar o aplicativo para que haja um ponto limpo no qual os testes possam injetar itens falsos que imitem o comportamento real do Cloud Firestore. Felizmente, o widget Consumer é perfeito para o trabalho.
Abra o main.dart e substitua o widget MyApp pelo seguinte código:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: Consumer<ICounterManager>( builder: (context, manager, _child) => MyHomePage( manager: manager, title: 'Flutter Demo Home Page', ), ), ); }}
Além disso, importe o Provider para o início do main.dart:
import 'package:provider/provider.dart';
O agrupamento de MyHomePage em um widget Consumer permite chegar arbitrariamente alto na árvore de widgets para acessar os recursos desejados e injetá-los nos widgets que precisam deles. Esse pode parecer um trabalho desnecessário neste tutorial, porque você só precisa voltar uma camada até o MyApp(), mas isso pode abranger dezenas widgets em aplicativos reais.
Em seguida, no mesmo arquivo, faça esta edição em MyHomePage:
Observação: não se preocupe se vir uma tela vermelha depois de salvar essa mudança. Mais edições são necessárias para concluir a refatoração.
class MyHomePage extends StatefulWidget { final ICounterManager manager; MyHomePage({@required this.manager, Key key, this.title}) : super(key: key);
final String title;
@override _MyHomePageState createState() => _MyHomePageState();}
Essa mudança simples de construtor permite que o código aceite a variável transmitida no snippet anterior.
Por fim, conclua a refatoração fazendo esta edição em _MyHomePageState:
class _MyHomePageState extends State<MyHomePage> {
// No longer expect to receive a `ICounterManager object`
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( // Reference `widget.manager` instead of // `manager` directly '${widget.manager.state.count}', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( // Reference `widget.manager` instead of `manager` directly onPressed: () => setState(() => widget.manager.increment()), tooltip: 'Increment', child: Icon(Icons.add), ), ); }}
Observação: provavelmente será necessária uma reinicialização a quente para corrigir o aplicativo.
Como você deve se lembrar, todos os objetos State contêm uma referência para seus wrappers StatefulWidget no atributo widget. Portanto, o objeto _MyHomePageState pode acessar esse novo atributo manager mudando seu código de manager para widget.manager.
Pronto! Você injetou dependências nos widgets que precisam delas, em vez de fixar implementações de produção no código.
Se você executar o teste do Flutter agora mesmo, verá que o pacote de teste não é mais bem-sucedido. Quando você inspecionar o widget_test.dart, o motivo deve ser claro: a função de teste instancia o MyApp(), mas não o agrupa com o DependenciesProvider como você fez no código real. Por isso, o widget Consumer adicionado dentro do MyApp não consegue encontrar um Provider satisfatório em seus widgets ancestrais.
É nesse ponto que a injeção de dependências começa a demonstrar seus benefícios. Em vez de imitar o código de produção nos testes (agrupando o MyApp com o DependenciesProvider), altere o teste para inicializar o MyHomePage. Atualize o widget_test.dart da seguinte maneira:
import 'package:firebasecounter/counter_manager.dart';import 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';
import 'package:firebasecounter/main.dart';
void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget( MaterialApp( home: MyHomePage( manager: CounterManager(), title: 'Test Widget', ), ), );
// Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump();
// Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); });}
Com o uso direto de uma instância de MyHomePage (juntamente com o agrupamento do MaterialApp para fornecer objetos BuildContext válidos), você configurou uma integração com teste de unidade com o Cloud Firestore!
Até agora, você passou por um monte de códigos e introduziu várias classes auxiliares, mas não mudou nada no funcionamento do aplicativo. A boa notícia é que tudo está preparado para começar a escrever o código que reconhece o Cloud Firestore. Para começar, abra o pubspec.yaml e adicione estas duas linhas:
dependencies: # Add this cloud_firestore: ^0.14.1 # Add this firebase_core: ^0.5.0 flutter: sdk: flutter provider: ^4.3.2+2
Como sempre, quando se aplica mudanças ao pubspec.yaml (a menos que o ambiente de desenvolvimento integrado faça isso por você), é preciso executar o seguinte comando para fazer o download e vincular as novas bibliotecas:
Observação: se você ainda não tiver criado o banco de dados, acesse o Console do Firebase do projeto, clique na guia Cloud Firestore e clique no botão Create Database.
A primeira etapa para usar com êxito o Cloud Firestore é inicializar o Firebase e, o mais importante, não tentar usar nenhum recurso do Firebase até que essa tarefa tenha sido realizada com êxito. Por sorte, você pode conter essa lógica com um StatefulWidget em vez de espalhar isso por todo o código.
Crie um novo arquivo em firebasecounter/lib/firebase_waiter.dart e adicione o seguinte código:
import 'package:firebase_core/firebase_core.dart';import 'package:flutter/material.dart';
class FirebaseWaiter extends StatefulWidget { final Widget Function(BuildContext) builder; final Widget waitingChild; const FirebaseWaiter({ @required this.builder, this.waitingChild, Key key, }) : super(key: key);
@override _FirebaseWaiterState createState() => _FirebaseWaiterState();}
class _FirebaseWaiterState extends State<FirebaseWaiter> { Future<FirebaseApp> firebaseReady;
@override void initState() { super.initState(); firebaseReady = Firebase.initializeApp(); }
@override Widget build(BuildContext context) => FutureBuilder<FirebaseApp>( future: firebaseReady, builder: (context, snapshot) => // snapshot.connectionState == ConnectionState.done ? widget.builder(context) : widget.waitingChild, );}
Essa classe usa o padrão do Flutter de aproveitar determinados widgets para lidar completamente com uma dependência ou um problema específico dentro do aplicativo. Para usar esse widget FirebaseWaiter, volte ao main.dart e aplique a seguinte mudança em MyApp:
// Add this import at the topimport 'package:firebasecounter/firebase_waiter.dart';
// Replace `MyApp` with thisclass MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: FirebaseWaiter( builder: (context) => Consumer<ICounterManager>( builder: (context, manager, _child) => MyHomePage( manager: manager, title: 'Flutter Demo Home Page', ), ), // This is a great place to put your splash page! waitingChild: Scaffold( body: const Center(child: CircularProgressIndicator()), ), ), ); }}
Agora, o aplicativo pode aguardar pela inicialização do Firebase, mas pode ignorar esse processo durante os testes simplesmente não utilizando o FirebaseWaiter.
Observação: as mudanças acima podem fazer com que o Flutter reclame pela ausência de plug-ins do Firebase. Se isso acontecer, encerre completamente o aplicativo e reinicie a depuração, para que o Flutter possa instalar todas as dependências específicas de plataforma.
Primeiro, importe o Cloud Firestore adicionando a seguinte linha ao início do counter_manager.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
Em seguida, também no counter_manager.dart, adicione a seguinte classe:
class FirestoreCounterManager implements ICounterManager { AppState state; final FirebaseFirestore _firestore;
FirestoreCounterManager() : _firestore = FirebaseFirestore.instance, state = const AppState() { _watchCollection(); }
void _watchCollection() { // Part 1 _firestore .collection('clicks') .snapshots() // Part 2 .listen((QuerySnapshot snapshot) { // Part 3 if (snapshot.docs.isEmpty) return; // Part 4 final _clicks = snapshot.docs .map<DateTime>((doc) { final timestamp = doc.data()['timestamp']; return (timestamp != null) ? (timestamp as Timestamp).toDate() : null; }) // Part 5 .where((val) => val != null) // Part 6 .toList(); // Part 7 state = AppState(_clicks); }); }
@override void increment() { _firestore.collection('clicks').add({ 'timestamp': FieldValue.serverTimestamp(), }); }}
Observação: essa classe é quase correta, mas cria um bug que será explorado mais adiante. Se você adicionar esse código ao aplicativo e executá-lo agora, verá que o comportamento não é o que você desejava. Continue lendo para ver uma explicação completa do que está acontecendo.
Há muitas coisas acontecendo aqui, então, vamos falar sobre elas.
Primeiro, o FirestoreCounterManager implementa a interface do ICounterManager, por isso ele é um candidato qualificado para uso em widgets de produção. (Eventualmente, ele receberá o byDependenciesProvider.) O FirestoreCounterManager também mantém uma instância do FirebaseFirestore, que é a conexão ao vivo com o banco de dados de produção. Além disso, o FirestoreCounterManager chama _watchCollection() durante sua inicialização para configurar uma conexão com os dados específicos nos quais você está interessado, e é neste ponto que as coisas ficam interessantes.
O método _watchCollection() é muito útil e merece um exame mais cuidadoso.
Na Parte 1, _watchCollection() chama _firestore.collection('clicks').snapshots(), que retorna um stream de atualizações sempre que há uma mudança nos dados de hora da coleção.
Na Parte 2, _watchCollection() registra imediatamente um listener para esse stream utilizando .listen(). A callback transmitida para listen() recebe um novo objeto QuerySnapshot em cada mudança dos dados. Esse objeto de atualização é chamado de instantâneo porque reflete o estado correto do banco de dados em um determinado momento, mas pode ser substituído por um novo instantâneo a qualquer ponto.
Na Parte 3, a callback entra em curto-circuito se a coleção estiver vazia.
Na Parte 4, a callback faz o loop para os documentos do instantâneo e retorna uma lista de valores nulos e DateTime combinados.
Na Parte 5, a callback descarta todos os valores nulos. Esses valores vêm do bug que será corrigido em breve, mas esse tipo de codificação defensiva é sempre uma boa ideia quando estamos lidando com dados do Cloud Firestore.
Na Parte 6, a callback lida com o fato de que map() retorna um iterador, não uma lista. A chamada de .toList() em um iterador o força a processar toda a coleção, que é aquilo que desejamos fazer.
E, por fim, na Parte 7, a callback atualiza o objeto state.
Para usar a nova classe, abra o dependencies.dart e substitua o conteúdo do arquivo pelo seguinte:
@override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<ICounterManager>( create: (context) => FirestoreCounterManager()), ], child: child, ); }}
Se você executar esse código como está, poderá quase ver o comportamento desejado. Tudo parece correto, com a exceção de que a tela sempre é renderizada um clique atrás da realidade. O que está acontecendo?
O problema vem de uma incompatibilidade entre a implementação inicial do contador e a implementação atual, baseada em stream. O gerenciador onPressed do FloatingActionButton tem a seguinte aparência:
floatingActionButton: FloatingActionButton( onPressed: () => setState(() => widget.manager.increment()), ...)
Esse gerenciador chama increment() e invoca imediatamente setState(), que instrui o Flutter a renderizar novamente.
Isso funcionava bem durante a atualização síncrona de estados mantidos na memória do dispositivo. No entanto, a nova implementação baseada em stream inicia uma série etapas assíncronas. Isso significa que, da forma como está, o código chama setState() imediatamente, depois, apenas em um ponto desconhecido no futuro, o objeto manager atualiza seu atributo de estado. Em resumo, a chamada a setState() no gerenciador onPressed está ocorrendo cedo demais. E, o que é pior, uma vez que toda essa atividade ocorre dentro de uma callback, dentro do FirestoreCounterManager, sobre o qual os widgets não sabem nada, não há um Future pelo qual os widgets possam aguardar para solucionar o problema.
É quase como se o objeto manager precisasse ser capaz de dizer aos widgets quando redesenhar. 🤔
É aí que entra o ChangeNotifier.
Observação: caso você encontre obstáculos nesta seção, compare suas mudanças com este commit no repositório público. Elas incluem mudanças do Xcode e do build.gradle resultantes da adição do Firebase, mas você provavelmente pode se concentrar mais nas mudanças dos arquivos Dart.
ChangeNotifier é uma classe que faz exatamente o que seu nome sugere: notifica os widgets quando ocorrem mudanças que exigem uma nova renderização.
A primeira etapa do processo é atualizar a interface do ICounterManager para estender ChangeNotifier. Para isso, abra firebasecounter/lib/counter_manager.dart e faça as seguintes mudanças na declaração ICounterManager:
// Add `extends ChangeNotifier` to your declarationabstract class ICounterManager extends ChangeNotifier { // Everything inside the class is the same.}
Se você ainda não tiver importado o flutter/material.dart, abra firebasecounter/lib/counter_manager.dart e adicione-o ao início:
import 'package:flutter/material.dart';
Agora, você está pronto para atualizar as definições de CounterManager e FirestoreCounterManager. Para o CounterManager, substitua seu código pela seguinte implementação:
class CounterManager extends ChangeNotifier implements ICounterManager { AppState state = AppState();
/// Copies the state object with the timestamp of the most /// recent click and tells the stream to update. void increment() { state = state.copyWith(DateTime.now()); // Adding this line is how `ChangeNotifier` tells widgets to // re-render themselves. notifyListeners(); }}
E, para o FirebaseCounterManager, aplique as seguintes mudanças:
class FirestoreCounterManager extends ChangeNotifier implements ICounterManager { ...}
2. Adicione a mesma linha notifyListeners(); ao final de _watchCollection(), da seguinte maneira:
void _watchCollection() { _firestore .collection('clicks') .snapshots() .listen((QuerySnapshot snapshot) { // Generation of `_clicks` omitted for clarity, but do not // change that code.
state = AppState(_clicks); // The only change necessary is to add this line! notifyListeners(); });}
Agora, você já configurou metade das mudanças necessárias para que as classes do ICounterManager instruam os widgets a renderizar novamente sempre que os dados mudarem. As classes Manager estão instruindo os widgets a renderizar novamente, mas, se você executar o aplicativo agora, verá que os widgets não estão ouvindo.
Para corrigir isso, abra o dependencies.dart e substitua a implementação de DependenciesProvider pelo seguinte:
@override Widget build(BuildContext context) { return MultiProvider( providers: [ // `Provider` has been replaced by ChangeNotifierProvider ChangeNotifierProvider<ICounterManager>( create: (context) => FirestoreCounterManager(), ), ], child: child, ); }}
Como última mudança, remova setState de _MyHomePageState para ignorar qualquer nova renderização desnecessária. Atualize FloatingActionButton desta forma:
floatingActionButton: FloatingActionButton( // Remove setState()! onPressed: widget.manager.increment, tooltip: 'Increment', child: Icon(Icons.add), ),
Pronto! O ChangeNotifierProvider garante que os widgets sejam “listeners”, para que, quando notifyListeners() for chamado por uma classe do ICounterManager, os widgets recebam a mensagem para fazer a renderização novamente.
Neste ponto, você deve ser capaz de reiniciar a quente o aplicativo e ver tudo funcionando corretamente.
Observação: caso você encontre obstáculos nesta seção, compare suas mudanças com este commit no repositório público.
Embora a última rodada de mudanças tenha implementado com êxito a funcionalidade desejada, infelizmente, ela também prejudicou os testes. Em seguida, você aplicará alguns ajustes adicionais para que tudo funcione novamente, e assim, terá terminado.
No widget_test.dart, o código transmite uma instância do CounterManager diretamente sem um ChangeNotifierListener auxiliar. A forma como isso era tratado na árvore de widgets era agrupando tudo no DependenciesProvider, mas essa classe tem conhecimento do Firestore, e o objetivo disto tudo é manter o Firestore de fora dos testes.
Uma solução é criar o TestDependenciesProvider, que pode conter as versões de teste de todas as dependências. Abra firebasecounter/lib/dependencies.dart e adicione a seguinte classe:
class TestDependenciesProvider extends StatelessWidget { final Widget child; TestDependenciesProvider({@required this.child});
@override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider<ICounterManager>( create: (context) => CounterManager(), ), ], child: child, ); }}
Essa classe é quase idêntica ao DependenciesProvider, mas o TestDependenciesProvider fornece uma instância de CounterManager() em vez de FirestoreCounterManager().
Agora, em test/widget_test.dart, atualize a inicialização do widget de teste para:
await tester.pumpWidget( TestDependenciesProvider( child: MaterialApp( home: Consumer<ICounterManager>( builder: (context, manager, _child) => MyHomePage( manager: manager, title: 'Flutter Test Home Page', ), ), ), ),);
Se ainda não tiver feito isso, adicione estes dois imports perto do início do test/widget_test.dart:
import 'package:firebasecounter/dependencies.dart';import 'package:provider/provider.dart';
Execute os teste novamente e... pronto!
Neste artigo, você remodelou o aplicativo contador clássico do Flutter para que ele mantenha toda a atividade no Cloud Firestore. Também evitou misturar a lógica de negócios com os widgets, o que facilita o teste do aplicativo.
As técnicas de gerenciamento de estados discutidas aqui são viáveis para muitos aplicativos, mas não são as únicas formas de se fazer isso, nem as melhores. A comunidade do Flutter está repleta de excelentes soluções de gerenciamento de estados que valem uma investigação. Estas são algumas a serem consideradas:
Para obter mais informações sobre gerenciamento de estados, consulte os documentos sobre gerenciamento de estados no flutter.dev.
Seja qual for a solução de gerenciamento de estados escolhida, boa sorte e boa codificação!
Este é o Now in Android, seu guia atualizado de novidades e fatos importantes do mundo de desenvolvimento no Android.
Recentemente, foi lançado o build estável do Android Studio 4.1. Já falei sobre essa versão em outros episódios, enquanto ela percorria seu caminho pelas versões anteriores à estável, mas aqui vão alguns destaques:
Você pode saber mais sobre essa versão no vídeo de Yacine Rezgui e também no blog de Scott Swarthout e nas notas da versão do Android Studio. Ou pode simplesmente fazer o download da coisa e conferir você mesmo.
Android Studio 4.1
Lançamos uma nova série sobre Modern Android Development (MAD, ou desenvolvimento moderno no Android) chamada MAD Skills. A série terá conteúdo em formatos de vídeo e artigo sobre várias partes do MAD, incluindo linguagem (Kotlin), ferramentas (Android Studio), APIs (um subconjunto do Jetpack) e distribuição (Android App Bundles). Em intervalos de algumas semanas, daremos início a uma nova minissérie sobre um tópico específico.
Esta semana, começamos com uma série sobre o componente de navegação. No primeiro episódio, forneço uma visão geral da API e da ferramenta. O segundo episódio mostra como navegar para destinos de diálogo. E, na próxima semana, postaremos episódios que falam sobre SafeArgs e links diretos.
Confira a playlist do MAD Skills para ver os programas que já foram postados e fique de olho nos novos episódios que serão postados semanalmente até… bem, ainda não planejamos uma data final. Mas temos muito conteúdo técnico para discutir, portanto, acho que o fim está distante.
Para os que preferem o conteúdo em formato de artigo, sempre que for publicado um vídeo falando sobre um material que ainda não foi descrito em nenhum artigo, postaremos um artigo auxiliar na publicação Android Developers no Medium. Portanto, fique ligado nos futuros artigos sobre MAD.
Lembre-se: não fique de fora, venha para o MAD!
Foram disponibilizados dois novos episódios da série sobre o Vocabulário do Kotlin.
Florina Muntenescu postou um artigo e um vídeo que ensinam como funcionam os argumentos padrão do Kotlin. Os argumentos padrão são um recurso de linguagem poderoso do Kotlin que permitem a redução do número de funções sobrecarregadas (imagine uma realidade alternativa onde o View.java pudesse se virar com apenas um construtor, em vez de quatro!) e também um código de chamada mais simples quando casos comuns podem depender de padrões razoáveis.
Don’t argue with default arguments
Murat Yener também postou um artigo e um vídeo sobre o recurso delegate do Kotlin. Os delegates podem ser usados para transferir trabalho para outro código. O artigo mostra exemplos de delegates de classe (nos quais uma classe pode se submeter totalmente a outra) e delegates de propriedade (nos quais o código pode se submeter a outro objeto para fornecer as funções get/set subjacentes para uma propriedade).
O Kotlin fornece não só a palavra-chave de infraestrutura e linguagem (ou seja, o by), mas também vários delegates integrados (como o by lazy). Mas este artigo fala apenas sobre o “Como funciona?” e... delega as explicações sobre os delegates integrados a um artigo futuro.
Delegating Delegates to Kotlin
Caren Chang postou um artigo para ajudar você a entender como dar suporte aos novos recursos e requisitos do Play Faturamento relacionados à aquisição e retenção de assinantes. Essas mudanças entrarão em vigor no dia 1º de novembro. Portanto, se o seu app vende produtos por assinatura, é bom saber logo como você deve proceder.
Preparing Your Apps for the latest features in Google Play’s billing system
Isai Damier postou uma série de artigos em duas partes: Biometric Authentication on Android.
A Parte 1 discute por que você deve considerar a incorporação da autenticação biométrica. Por exemplo: se o seu app exige que os usuários façam login com frequência, oferecer a autenticação biométrica torna essa experiência mais rápida e simples para eles. Ou, se o app exige login apenas uma vez após a instalação (provavelmente porque ficar fazendo login com uma senha é tedioso), a autenticação biométrica pode ser uma maneira de fornecer mais segurança aos usuários por meio de um mecanismo de login mais conveniente do que as senhas tradicionais.
Este artigo também apresenta o uso da API BiometricPrompt (na biblioteca AndroidX Biometric) para lidar com a autenticação.
Biometric Authentication on Android — Part 1
A Parte 2 fala sobre alguns dos detalhes do uso da API e também sobre o fluxo de design recomendado para usuários com autenticação.
Biometric Authentication on Android — Part 2
O programa Motion Tags postou mais um episódio que fala sobre o KeyPosition. A tag KeyPosition especifica informações de layout durante uma animação MotionLayout. Você pode ver os episódios da série já postados na playlist do Motion Tags.
Publicamos outro episódio do Android Developers Backstage desde o último Now in Android. Confira no link abaixo ou acesse seu cliente de podcast favorito:
Desta vez, Tor Norbye, Romain Guy e eu conversamos com Ryan Mitchell, da equipe de bibliotecas, sobre recursos, incluindo como a ferramenta aapt2 trabalha.
Episódio 150: Aaptly Named
Isso é tudo, por enquanto. Faça o download da versão estável do Android Studio mais recente. Confira a série MAD Skills! Conheça os recursos de linguagem do Kotlin, como os argumentos padrão e os delegates. Veja como dar suporte aos novos recursos e requisitos do Google Play para assinaturas de clientes. Saiba como e por que usar a autenticação biométrica nos apps. Assista ao vídeo Motion Tags mais recente para conhecer o KeyPosition! Ouça o episódio mais recente do podcast do ADB. Em breve, voltaremos com a próxima atualização do universo dos desenvolvedores Android.
No dia 2 de novembro, todas as atualizações de apps precisarão segmentar a API de nível 29 ou superior. A maioria das atualizações de apps já foi segmentada. Se a sua ainda não é uma delas, este é o momento perfeito para fazer isso. Porque, no dia 3 de novembro, já será tarde demais.
Para obter mais informações, confira o guia sobre como atender ao requisito de segmentação de SDK do Google Play.
O AndroidX recebeu as atualizações de costume de várias versões Alfa, Beta e RC intermediárias, mas vale a pena destacar também algumas bibliotecas que foram lançadas em versões estáveis:
A Unidade 2 do curso de noções básicas do Android no Kotlin já está disponível. Em um mesmo curso, pessoas sem experiência de desenvolvimento podem aprender sobre desenvolvimento de software, Android e Kotlin.
A Unidade 1: noções básicas do Kotlin para Android abrange itens básicos como classes, objetos e condicionais, e também ensina a usar imagens e texto em um app Android.
A Unidade 2: layouts apresenta conceitos de IU que incluem layouts XML, Material Design, obtenção de entradas do usuário e uso do RecyclerView. Os alunos criarão dois apps diferentes com esses recursos e muito mais.
Ah, e não vamos nos esquecer do preço: É GRATUITO! Esse é o meu preço favorito!
Você que é desenvolvedor precisa conferir alguns artigos recentes sobre o Google Play.
Sameer Samat publicou um artigo que esclarece alguns requisitos e políticas para apps que utilizam a Play Store. O artigo fala, por exemplo, sobre a hospedagem na Play Store e em outras app stores e sobre os requisitos relacionados ao uso do Play Faturamento para compras de produtos e softwares digitais no aplicativo.
Listening to Developer Feedback to Improve Google Play
Para ajudar na compreensão de alguns dos detalhes do artigo anterior, Mrinalini Loew também publicou um artigo de Perguntas frequentes com perguntas (e respostas!) sobre essas e outras políticas da Play Store.
Answering your FAQs about Google Play billing
E, se você quiser saber mais sobre como usar o Play Faturamento, confira a série de Caren Chang:
Quem utiliza o Play Console provavelmente já teve a chance de conferir a nova versão do console, que está na versão Beta desde o mês de junho. Como geralmente acontece com os produtos Beta (pelo menos esse é o ideal), o Play Console deixará a versão Beta e será lançado na versão estável daqui a um mês, no dia 2 de novembro. O Play Console antigo deixará de existir, e todos utilizarão a nova versão.
Essa nova versão oferece vários recursos, incluindo IU e experiência extremamente melhoradas (resultado de um esforço de reprojeto total).
Se quiser conferir a nova versão antes do lançamento, acesse play.google.com/console. Ou você pode ficar aí roendo as unhas e esperar até que o novo console se torne o único console, no dia 2 de novembro.
Todos os desenvolvedores receberão o novo Google Play Console em 2 de novembro de 2020
Recentemente, postamos alguns conteúdos específicos para desenvolvedores que criam jogos para Android:
A compactação de texturas é uma tecnologia útil para jogos. Com a utilização de diferentes formatos, os desenvolvedores podem atingir tamanhos menores de download e tempo de execução, além de melhorar o desempenho em tempo de execução. Mas nem todos os dispositivos dão suporte a todos os formatos de textura possíveis. Então, o que o desenvolvedor deve fazer?
Com o Play Asset Delivery, agora é possível usar vários formatos de textura diferentes no pacote do app, e ele fará o download da versão apropriada com base na capacidade do dispositivo do usuário. Para acessar uma breve visão geral da compactação de texturas e também detalhes sobre como tirar proveito desse novo recurso de distribuição de jogos, confira este artigo de Daniel Galpin.
Improve Your Game with Texture Compression Format Targeting
Se você preferir ver o conteúdo em formato de vídeo, as mesmas informações podem ser encontradas aqui, no Game Dev Show de Daniel:
Por falar em texturas e desenvolvimento de jogos, Francesco Carucci postou um vídeo no Game Dev Show que mostra como usar um dos recursos da nova ferramenta Android GPU Inspector para rastrear problemas de desempenho relacionados ao uso de texturas.
As texturas são o coração dos gráficos renderizados pelos jogos e, consequentemente, podem estar no centro dos problemas de desempenho. O vídeo fornece um bom exemplo do uso do GPU Inspector e fala sobre alguns dos problemas relativos à largura de banda, cache e filtragem e também sobre como esses problemas aparecem na ferramenta.
Meghan Mehta postou o primeiro episódio do que, esperamos, será uma série permanente sobre o RecyclerView.
O RecyclerView já tem muitos recursos, incluindo um guia do RecyclerView, amostras em Kotlin e Java e, é claro, a própria documentação de referência. Mas, se você só quiser criar um RecyclerView básico para exibir, por exemplo, alguns itens de texto, ou se apenas quiser ter uma visão geral, esse artigo pode ser o ideal. E, se quiser ver a amostra de código por trás, confira também o projeto no GitHub.
Getting to know RecyclerView
Calin Juravle publicou um artigo que fala sobre (e esclarece) vários mitos sobre como melhorar o desempenho de apps Android, como tamanho/inicialização de apps Kotlin versus Java, campos versus getters/setters, lambdas versus classes internas e o uso de pools de objetos. Os detalhes podem ser encontrados no artigo, mas aqui vai um spoiler do ponto mais importante: crie o perfil do app (da versão não depurada!) antes de decidir em quais otimizações deve investir tempo. Pode ser que você acabe desperdiçando o seu tempo em vez de poupar o dos usuários.
Busting Android performance myths
A equipe de segurança do Android postou um artigo que explica os diferentes níveis de autenticação e detalha as APIs biométricas introduzidas no Android P, Android 10 e Android 11.
Lockscreen and authentication improvements in Android 11
Isso é tudo, por enquanto. Agora, corra para atualizar seu app para o targetSdk 29! Confira as novas versões estáveis do AndroidX! Faça a Unidade 2 do curso de noções básicas do Android no Kotlin. Leia sobre as políticas da Play Store e o novo Play Console. Aprenda algumas técnicas de desenvolvimento de jogos muito legais para mapas de texturas. Leia artigos sobre o RecyclerView, desempenho e segurança. Em breve, voltaremos com a próxima atualização do universo dos desenvolvedores Android.
Postado por Steve Suppe, gerente de produtos do Google Play
A publicação de um app ou jogo é um dos momentos mais importantes do ciclo de vida de um app. Tudo precisa correr sem problemas, desde a garantia de estabilidade da versão de produção até a liberação rápida de versões de teste e a criação da mensagem de marketing perfeita.
Por isso, a visibilidade é fundamental. Saber quando o app está em fase de revisão, quando ele é aprovado e quando pode ser ativado no Google Play ajuda a definir um cronograma.
Agora, com dois novos recursos do novo Google Play Console, você pode fazer exatamente isso. A página Visão geral de publicações ajuda a compreender melhor o processo de publicação, e a Publicação gerenciada oferece mais controle sobre quando as atualizações do app são ativadas no Google Play. Quando o novo Play Console for disponibilizado de forma geral, no dia 2 de novembro, esses recursos serão a forma recomendada de controlar o momento do lançamento. Então, vamos dar uma olhada melhor neles.
Visão geral de publicações
A nova página Visão geral de publicações exibe todas as mudanças recentes feitas nas versões, listagens de lojas e muito mais, inclusive aquelas que estão sendo revisadas ou processadas no momento pelo Google Play. No caso de equipes maiores, isso significa que agora é possível coordenar todas as mudanças em um só local e publicar tudo ao mesmo tempo.
Ao contrário do registro de atividades do desenvolvedor, a Visão geral de publicações só mostra as mudanças que estarão visíveis no Google Play. Foi dessa forma que os desenvolvedores disseram que gostariam que considerássemos e revisássemos os apps.
Essas mudanças são organizadas por tipo de mudança ou faixa de lançamento, para uma rápida compreensão.
Publicação gerenciada Muitos já devem estar familiarizados com a publicação cronometrada do antigo Play Console. No novo Play Console, substituímos a publicação cronometrada pela Publicação gerenciada para oferecer uma experiência de publicação mais clara e previsível.
Após a ativação da Publicação gerenciada, as mudanças aprovadas só serão ativadas quando você decidir, em vez de automaticamente, após a revisão e o processamento. Isso permite enviar mudanças muito antes da data de lançamento prevista. Assim, você tem mais tempo para revisar ou fazer mudanças sem sacrificar o controle sobre a data de publicação.
Quando a publicação gerenciada está ativa, a página Visão geral de publicações apresenta duas seções: uma mostrando quais mudanças foram aprovadas e estão prontas para publicação e outra que mostra as mudanças que ainda estão em revisão.
Também fizemos algumas melhorias que têm sido solicitadas por muitos desenvolvedores:
● Agora, é possível publicar as mudanças aprovadas mesmo que outras ainda estejam em revisão. Antes, a publicação cronometrada não permitia ativar nenhuma mudança até que todas tivessem sido aprovadas.
● É possível ativar ou desativar a Publicação gerenciada a qualquer momento, mesmo que haja mudanças em revisão ou prontas para publicação. Não é mais preciso esperar pelas revisões pendentes para usar a Publicação gerenciada.
Em breve, será possível ver o ícone de Publicação gerenciada na navegação à esquerda, ao lado da Visão geral de publicações. Assim, será possível saber se a Publicação gerenciada está ativa em qualquer local do Play Console.
Para saber mais sobre publicações com o novo Play Console, incluindo cenários nos quais esses recursos serão mais úteis, confira este curso daPlay Academy. E, se ainda não tiver feito isso, faça a atualização para o novo Play Console em play.google.com/console e experimente a Publicação gerenciada.
Projete e crie aplicativos que transformam aquela ideia anotada no guardanapo em um produto capaz de encantar os usuários
Padmini Murthy
5 de out. · Leitura de 7 minutos
Esta postagem é uma coautoria de Padmini Murthy e Carol Soler
No Google Play, você já deve ter ouvido falar sobre as três fases principais da jornada de um desenvolvedor ao longo da criação, do envolvimento e da expansão de um aplicativo e da base de usuários. Com o ecossistema de conhecimento do Google Play, é possível mapear facilmente a trajetória por essas três fases do ciclo de vida de um aplicativo. Nele, você encontra programas de aprendizado, artigos sobre liderança de pensamento, práticas recomendadas, podcasts, documentos de suporte e muito mais. Compartilhamos artigos de especialistas para gerenciar a jornada do usuário em um aplicativo desde a instalação até o desligamento do usuário.
No entanto, neste artigo, nosso foco está nos desenvolvedores (ou editores) e nos desafios que eles enfrentam. A criação e manutenção de experiências digitais de alta qualidade envolvem um volume considerável de aspectos dinâmicos, desde o projeto e o desenvolvimento até o lançamento e a monetização. Falaremos aqui sobre esses “princípios de pensamento” nas várias fases da jornada de desenvolvimento. Essas são as bases principais para navegar com facilidade por essas fases.
Na primeira parte desta série “Crie aplicativos fantásticos”, falaremos sobre as fases primárias de projeto e criação.
Você teve uma ideia para seu próximo aplicativo. Fantástico! E agora? No Google Play, acreditamos que o primeiro passo lógico deve ser a criação do protótipo de UX. Esse não é apenas um dos aspectos primários do projeto, mas também um componente essencial da estratégia de produto para criar uma experiência do usuário de alta qualidade.
Esta é nossa lista de verificação de “princípios de pensamento” para a primeira fase básica do projeto:
Pense nas emoções — As melhores experiências do usuário são aquelas que falam ao coração e resolvem uma grande necessidade. E, para criar esse tipo de experiência, é fundamental fazer uma pesquisa inicial no segmento-alvo para saber o que seria mais valorizado no aplicativo. O próximo foco deve estar no projeto da UX, para ajudar os usuários a navegar pelo aplicativo e chegar ao local desejado da forma mais eficiente e intuitiva possível. É quase como se o aplicativo reconhecesse a psicografia dos usuários e adaptasse as personalizações a ela.
Pense nos vários formatos — Todo mundo sabe que, hoje em dia, é preciso se preparar para fornecer uma experiência maravilhosa em vários formatos. É claro que os smartphones, tablets e relógios Android são os padrões, mas é preciso adicionar a essa lista as experiências em Android Auto, Android TV e ARCore. O aplicativo precisa estar preparado para proporcionar uma experiência coesa em todos esses dispositivos. Aqui, você encontra um guia para otimizar o projeto para uma experiência em tela grande. Confira também um guia para começar a considerar todos os formatos do Android e preparar os aplicativos para eles.
Pense em projetos centrados na conversão — Na Google, criamos o Material Design como um sistema de envolvimento que ajuda a criar experiências perfeitas para os usuários e a incentivar a conversão. Por meio da escolha certa de componentes, ícones, transições visuais, temas, transições e outras compilações de usabilidade, agora é possível criar uma experiência digital intuitiva e envolvente.
Este é um exemplo de como o Trip.com usou o Material Design para criar uma excelente experiência de viagens e reservas em todo o mundo. Os princípios de projeto também ajudam a criar a navegação e exploração certas, fazer pesquisas, avaliações de produtos e comparações no aplicativo e configurar sistemas de registro e pagamento.
Pense na imersão com 3D e AR/VR — A realidade virtual e a realidade aumentada estão sendo cada vez mais utilizadas para a criação de uma “experiência de imersão”, principalmente nos segmentos de saúde, beleza, logística, entretenimento e comércio eletrônico. Com isso, hoje é possível visualizar com facilidade como uma cor de batom ficaria em uma pessoa ou a que distância alguém está de um museu escondido em uma rua menos conhecida. Do ponto de vista da realidade virtual, imagine a experiência quase realista de visitar sua residência de férias preferida nas Bahamas ou de jogar seu esporte ou videogame favorito, como o golfe ou o Roblox, por exemplo. Este é um guia para começar a considerar a criação de uma experiência de imersão usando AR e VR.
O Android Studio talvez seja a forma mais simples criar e lançar aplicativos Android, e os desenvolvedores em todo o mundo comprovam isso. Projetado especificamente para aplicativos Android com editores visuais, analisadores de APKs, criadores de perfis em tempo real e emuladores, ele oferece tudo o que os desenvolvedores precisam para dar vida a seus aplicativos. Veja aqui um guia completo para escrever, compilar, configurar, testar e depurar aplicativos Android e um guia de todas as ferramentas do Android Studio para desenvolvedores. Trabalhamos constantemente para adicionar recursos novos e aprimorados ao Android Studio, e ele traz novidades para os desenvolvedores com bastante frequência. Veja as novidades do Android Studio 4.0 aqui. Vamos criar seu primeiro aplicativo Android agora mesmo!
Seja qual for a ferramenta utilizada para criar um aplicativo Android, estes são alguns “princípios de pensamento” a serem considerados na fase de criação:
Pense no desempenho — Tamanhos de APK, o peso dos dados (potencialmente devido à integração com serviços de terceiros, como gateways de pagamento ou APIs de mapas) e recursos avançados afetam o desempenho e o tempo de carregamento dos aplicativos. Na maior parte do tempo, um baixo desempenho de aplicativo pode resultar em desinstalações e levar a um volume significativo de desligamentos de usuários.
Isso se torna mais crítico no caso do comércio eletrônico e de outros aplicativos que dependem de conversão. Uma prática recomendada pela Google é a avaliação dos recursos para fazer concessões inteligentes a fim de atingir o melhor desempenho possível. Os Android App Bundles ajudam a gerenciar o tamanho do APK, ou seja, a entregar uma experiência excelente ao segmento-alvo com um aplicativo menor. Vale lembrar a história da Netflix, que reduziu em mais de 33% o tamanho do aplicativo com os App Bundles. Também recomendamos conferir o Android Vitals para monitorar o desempenho e a estabilidade dos aplicativos.
Pense na qualidade — Essa é talvez a etapa mais importante do processo de compilação. Veja aqui algumas diretrizes principais de qualidade de aplicativos como referência. A garantia da qualidade de acordo com as diretrizes específicas de dispositivo e os formatos deve ser o foco antes do lançamento. Veja aqui uma bíblia completa de diretrizes de qualidade do Google Play. Recomendamos testar o aplicativo Android levando em conta estas diretrizes para ter uma excelente experiência do usuário e exposição adicional ao Wear OS, Android TV e Android Auto.
Pense em política e segurança — A segurança do ecossistema Android é fundamental, tanto para usuários quanto para desenvolvedores. Queremos ajudar a criar aplicativos seguros e em conformidade com políticas que protegem desenvolvedores e usuários e reduzem as violações de políticas. Isso significa usar os protocolos de criptografia certos, apenas os serviços de API autorizados e autenticações robustas por meio de tokens para identificar sessões, em vez de identificadores de dispositivos. Significa, também, executar testes completos de segurança e manter a conformidade geral. E não se esqueça de manter seus SDKs atualizados, para assegurar que eles estejam de acordo com todas as políticas do Google Play mais recentes. Esta é uma visão geral breve de introdução às políticas do Google Play.
Pense no pré-lançamento — Por fim, mas não menos importante nesta lista, é preciso compreender o desempenho do aplicativo no pré-lançamento executando versões Beta e fazendo testes e iterações antes do lançamento. O relatório de pré-lançamento fornece uma visão geral dos erros e avisos, com recomendações de como resolvê-los. Sempre que um APK é publicado, é recebido um relatório de pré-lançamento, algo específico das versões Beta fechadas e das faixas de teste. O aplicativo deve ser configurado no Google Play Console para receber esses relatórios, que são gerados em nosso laboratório de testes. Um tanto vinculado ao pré-lançamento, o pré-registro tem o objetivo de gerar empolgação e conscientização sobre os aplicativos e jogos antes do lançamento. No entanto, há alguns requisitos específicos para o pré-registro e a avaliação de sua adequação a um aplicativo. Confira este programa de aprendizado e também este artigo para se preparar para o pré-registro.
Próximos episódios da série “Crie aplicativos fantásticos”
Em nosso próximo blog, falaremos sobre o que acontece depois do envio e da publicação de um APK no Google Play. Depois que o aplicativo é avaliado e ativado, é preciso promover a aquisição e gerar o envolvimento para estimular o uso e, depois, garantir a retenção. Isso significa várias coisas diferentes, e representa toda uma nova linha de pensamento. Os princípios de pensamento, neste caso, podem ser divididos em fases para a criação de um primeiro impulso de aquisição e envolvimento. Vamos nos aprofundar nas estratégias de retenção com promoções, ofertas e muito mais.
Fique ligado!
O que você acha?
Você tem ideias sobre como passar da concepção para o projeto e a criação? Comente abaixo ou poste um tweet usando a hashtag #AskPlayDev. Você receberá uma resposta do nosso perfil @GooglePlayDev, onde compartilhamos regularmente notícias e dicas sobre como ter sucesso no Google Play.