Como fiz um aplicativo de desktop no Flutter (+ bônus)

Recentemente, soube-me que a próxima versão do Flutter (1.9) foi lançada , o que promete vários benefícios, incluindo suporte antecipado para aplicativos da web.

No trabalho, estou desenvolvendo aplicativos móveis no React Native, mas olho para o Flutter com curiosidade. Para quem não conhece: agora, no Flutter, você pode criar aplicativos para Android e iOS, o suporte a aplicativos da Web está sendo preparado para lançamento e também há planos para oferecer suporte a computadores.

Tal é "um anel para governar tudo".

Depois de passar alguns dias pensando em que tipo de aplicativo você pode tentar fazer, decidi escolher uma tarefa com um asterisco - do que precisamos dessas faixas desgastadas? Balance na área de trabalho e supere heroicamente as dificuldades! Olhando para o futuro, direi que quase não houve dificuldades.

Sob o corte - uma história sobre como eu resolvi as tarefas usuais do programador React Native usando as ferramentas Flutter, além da impressão geral da tecnologia.



Pensando em quais recursos do Flutter eu gostaria de "tocar", decidi que no meu aplicativo deveria ser:

  • pedidos para a API remota;
  • transições entre telas;
  • Animações de transição
  • gerente de estado - redux ou algo semelhante.

Como não sei como fazer back-end, decidi procurar uma API aberta de terceiros. Como resultado, decidi por esse recurso - cursos CBR em XML e JSON, API . Bem, aqui finalmente decidi sobre a funcionalidade do aplicativo: haverá duas telas; na principal, há uma lista de moedas na taxa CBR; quando você clica em um item da lista, abrimos uma tela com informações detalhadas.

Preparação


Como o comando flutter create ainda não sabe como criar um projeto para Windows / Linux (atualmente apenas o Mac é suportado, use o sinalizador --macos ), você deve usar este repositório, onde há um exemplo preparado. Clonamos o repositório, pegamos a pasta de example partir daí, se necessário, renomeie-o e continuamos trabalhando nele.

Como o suporte a plataformas de desktop ainda está em desenvolvimento, você ainda precisa executar várias manipulações. Para acessar os recursos em desenvolvimento, execute no terminal:

 flutter channel master flutter upgrade 

Além disso, você precisa informar ao Flutter que ele pode usar sua plataforma:

 flutter config --enable-linux-desktop 

ou

 flutter config --enable-macos-desktop 

ou

 flutter config --enable-windows-desktop 

Se tudo correu bem, executando o comando flutter doctor você verá uma saída semelhante:



Então, o cenário está pronto, o público no salão - podemos começar.

Layout


A primeira coisa que chama a sua atenção após o React Native é a falta de uma linguagem de marcação especial no JSX. O Flutter obriga a escrever a marcação e a lógica comercial no Dart . No começo, isso é chato: a aparência não tem nada a ver, o código parece complicado e até mesmo esses colchetes estão no final do componente!

Por exemplo, como:



E este não é o limite! Vale a pena remover um no lugar errado e um passatempo agradável (não) é garantido para você.

Além disso, devido às peculiaridades dos componentes de estilo no Flutter, para componentes grandes, o recuo da borda esquerda do editor aumenta muito rapidamente e, com ele, o número de colchetes fechados.

Esse recurso é que, nos estilos Flutter, são os mesmos componentes (para ser mais preciso - widgets).

Se no React Native organizar três botões em uma linha dentro da View para distribuir uniformemente o espaço do contêiner, basta especificar flexDirection: 'row' para a View em estilos e adicionar flex: 1 para os botões nos estilos, então o Flutter possui um componente separado Row para organizar elementos em uma linha e uma separada para "expansibilidade" de um elemento em todo o espaço disponível: Expanded .

Como resultado, em vez de

 <View style={{height: 100, width:300, flexDirection: 'row'}}> <Button title='A' style={{flex:1}}> <Button title='B' style={{flex:1}}> <Button title='C' style={{flex:1}}> </View> 

nós temos que escrever assim:

 Container( height: 100, width: 300, child: Row( children: <Widget>[ Expanded( child: RaisedButton( onPressed: () {}, child: Text('A'), ), ), Expanded( child: RaisedButton( onPressed: () {}, child: Text('B'), ), ), Expanded( child: RaisedButton( onPressed: () {}, child: Text('C'), ), ), ], ), ) 

Mais detalhado, não é?

Ou, digamos, você deseja adicionar um quadro com bordas arredondadas neste contêiner. No React Native, simplesmente adicionamos aos estilos:

 borderRadius: 5, borderWidth: 1, borderColor: '#ccc' 

No Flutter, temos que adicionar algo assim aos argumentos do contêiner:

 decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(width: 1, color: Color(0xffcccccc)) ), 

Em geral, a princípio, minha marcação se transformou em enormes folhas de código, nas quais o diabo quebraria sua perna. No entanto, nem tudo é tão ruim.

Primeiro, é claro que os componentes grandes devem ser divididos - colocados em widgets separados ou pelo menos nos métodos da sua classe de widgets.

Em segundo lugar, o plug-in Flutter no VS Code ajuda muito - na figura acima, os comentários entre colchetes são assinados pelo próprio plug-in (e não podem ser excluídos), o que ajuda a não ficar confuso entre colchetes. Além disso, ferramentas de formatação automática - depois de meia hora, você se acostuma a pressionar periodicamente Ctrl+Shift+I para formatar o código.

Além disso, a sintaxe do idioma Dart na segunda edição se tornou muito mais agradável, então no final do dia eu já gostei de usá-lo. Incomum? Sim Mas não desagradável.

Solicitações de API


No React Native, para obter dados de alguma API, geralmente usamos o método fetch , que retorna o Promise nós.

Em Flutter, a situação é semelhante. Depois de examinar os exemplos na documentação, adicionei o pacote http ao pubspec.yaml (um análogo do package.json do mundo JS) e escrevi algo como isto:

 Future<http.Response> getAnything() { return http.get(URL); } 

O objeto Future tem um significado muito semelhante ao Promise, então tudo é bem transparente aqui. Bem, para serializar / desserializar objetos json, você pode usar o conceito de classes de modelo com métodos especiais de fromJSON / toJSON . Você pode ler mais sobre isso na documentação .

Transição entre telas


Apesar de eu estar criando um aplicativo de desktop, do ponto de vista do Flutter, não há diferença em qual plataforma ele está girando. Bem, isto é, no meu caso, é assim, em geral - eu não sei. De fato, a janela do sistema na qual o aplicativo de vibração é iniciada é a mesma tela de um smartphone.

A transição entre telas é bastante trivial: criamos uma classe de widget de tela e usamos a classe Navigator padrão.

No caso mais simples, pode ser algo como isto:

 RaisedButton( child: Text('Go to Detail'), onPressed: () { Navigator.of(context).push<void>(MaterialPageRoute(builder: (context) => DetailScreen())); }, ) 

Se o seu aplicativo tiver várias telas, é mais razoável primeiro preparar um dicionário de rotas e depois usar o método pushNamed . Um pequeno exemplo da documentação:

 class NavigationApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( ... routes: <String, WidgetBuilder>{ '/a': (BuildContext context) => usualNavscreen(), '/b': (BuildContext context) => drawerNavscreen(), } ... ); } } // AnyWidget ... onPressed: () { Navigator.of(context).pushNamed('/a'); }, ... 

Além disso, você pode preparar uma animação especial para alternar entre telas e escrever algo como isto:

 Navigator.of(context).push<void>(ScaleRoute(page: DetailScreen())); 

Aqui ScaleRoute é uma classe especial para criar animações de transição. Bons exemplos de tais animações podem ser encontrados aqui .

Gerenciamento de estado


Acontece que precisamos ter acesso a alguns dados de qualquer parte do nosso aplicativo. No React Native, o redux frequentemente usado (se não com maior frequência) para esses fins.

Para o Flutter, existe um repositório que mostra exemplos de uso de várias arquiteturas de aplicativos - existem Redux, MVC e MVU, e até aqueles sobre os quais eu nunca ouvi falar antes.

Depois de remexer um pouco nesses exemplos, decidi parar no Provider .

Em geral, a ideia é bastante simples: criamos uma classe especial que ChangeNotifier classe ChangeNotifier , na qual armazenaremos nossos dados, os atualizaremos usando os métodos dessa classe e os buscaremos a partir daí, se necessário. Veja a documentação do pacote para mais detalhes.

Para fazer isso, adicione o pacote do provider ao pubspec.yaml e prepare a classe Provider. No meu caso, fica assim:

 import 'package:flutter/material.dart'; import 'package:rates_app/models/rate.dart'; class RateProvider extends ChangeNotifier { Rate currentrate; void setCurrentRate(Rate rate) { this.currentrate = rate; notifyListeners(); } } 

Aqui Rate é minha classe de modelo de moeda (com name campos, code , value etc.), currentrate é o campo no qual a moeda selecionada será armazenada e setCurrentRate é o método pelo qual o valor da currentrate é currentrate .

Para anexar nosso provedor ao aplicativo, alteramos o código da classe do aplicativo:

 @override Widget build(BuildContext context) { return ChangeNotifierProvider( builder: (context) => RateProvider(), //   child: MaterialApp( ... ), home: HomeScreen(), ), ); } 

É isso, agora, se queremos salvar a moeda selecionada, escrevemos algo assim:

 Provider.of<RateProvider>(context).setCurrentRate(rate); 

E se queremos obter o valor armazenado, então isto:

 var rate = Provider.of<RateProvider>(context).currentrate; 

Tudo é bastante transparente e sem clichês (ao contrário do Redux). Obviamente, talvez para aplicações mais complexas tudo saia não tão bem, mas para aqueles como o meu exemplo, vinhos puros.

Criar aplicativo


Em teoria, o comando flutter build <platform> é usado para criar o aplicativo. Na prática, quando executei o comando flutter build linux , recebi esta mensagem:



"Não doeu", pensei, fiquei horrorizado com o peso da pasta de build - 287,5 MB - e, devido à simplicidade da minha alma, excluí esta pasta para sempre. Como se viu - em vão.

Após excluir o diretório de build , o projeto parou de iniciar. Como não consegui restaurá-lo, copiei-o do exemplo original. Não ajudou - o colecionador xingou os arquivos ausentes.

Após realizar uma pequena pesquisa, verificou-se que nessa pasta existe um arquivo snapshot_blob.bin.d , que aparentemente contém caminhos para todos os arquivos usados ​​no projeto. Eu adicionei os caminhos ausentes e funcionou.

Portanto, no momento, o Flutter não sabe como preparar versões de lançamento para a área de trabalho. Enfim, para Linux.

Em geral, se você fechar os olhos para esse sinal de menos, o aplicativo ficou do jeito que eu queria e parecia

tão


Bônus


Passamos para o bônus prometido.

Mesmo na fase de escrever o aplicativo, eu tinha o desejo de verificar o quão difícil seria portá-lo para outras plataformas. Vamos começar com o telefone celular.

Certamente existe um caminho menos bárbaro, mas decidi que o caminho mais curto é direto. Portanto, simplesmente criei um novo projeto Flutter, transferi o arquivo pubspec.yaml , os assets , fonts e diretórios lib para ele e adicionei a linha ao AndroidManifest.xml :

 <uses-permission android:name="android.permission.INTERNET" /> 

A aplicação começou com um meio chute e eu consegui isso

quadro


No começo, eu tive que mexer com a web. Como não sabia criar um projeto da web, usei as instruções da Internet, que por algum motivo não funcionaram. Eu já queria cuspir, mas me deparei com este manual.

Como resultado, tudo acabou sendo o mais simples possível - tudo o que era necessário era ativar o suporte para aplicativos da web. Aperte do manual:

 flutter channel master flutter upgrade flutter config --enable-web cd <into project directory> flutter create . flutter run -d chrome 

Depois transferi os arquivos necessários para esse projeto da mesma maneira bárbara e recebi

resultado


Impressões gerais


No começo, trabalhar com Flutter era incomum, tentei constantemente usar as abordagens usuais do React Native, e isso interferiu. Além disso, alguma redundância do código do dardo era um pouco irritante.

Depois que peguei minha mão (e cones) um pouco, comecei a ver as vantagens do Flutter sobre o React Native. Vou listar alguns.

Idioma . O dardo é uma linguagem completamente compreensível e agradável, com forte digitação estática. Após o JavaScript, foi como uma lufada de ar fresco. Parei de ter medo de que meu código quebrasse em tempo de execução e foi uma sensação agradável. Alguém pode dizer que há Flow e TypeScript, mas não é isso - em nossos projetos, usamos os dois, e algo sempre quebrava em algum lugar. Quando escrevo no React Native, não consigo deixar de sentir que meu código está em adereços de palitos de fósforo que podem quebrar a qualquer momento. Com Flutter, esqueci esse sentimento e, se o preço é redundância de código, estou pronto para pagá-lo.

Plataforma . No React Native, você usa componentes nativos e isso geralmente é bom. Mas, por causa disso, às vezes você precisa escrever um código específico da plataforma, bem como capturar bugs específicos para cada plataforma. Pode ser incrivelmente cansativo. Com o Flutter, você pode esquecer esses problemas como um pesadelo (embora possa ser que em aplicativos grandes as coisas não sejam tão suaves).

O meio ambiente . Com o ambiente no React Native, tudo é triste. Os plugins vscode caem constantemente, o depurador pode consumir 16 GB de operação e 70 GB de swap, travando firmemente o sistema (por experiência pessoal) e o cenário de correção de erros mais comum: "remova node_modules, instale pacotes novamente e tente reiniciar várias vezes". Isso geralmente ajuda, mas bljad! Não é assim que deve ser, não é assim.

Além disso, você precisará executar o AndroidStudio e o Xcode periodicamente, porque alguns são colocados dessa maneira (para ser justo, com o lançamento do RN 0.60, isso ficou melhor).

Nesse contexto, o plug-in oficial do Flutter para vscode parece muito bom. As dicas de código permitem que você se familiarize com a plataforma sem consultar a documentação, a formatação automática resolve o problema com o estilo de codificação, o depurador normal, etc.
Em geral, parece uma ferramenta mais madura.

Plataforma cruzada . O React Native professa o princípio “Aprenda uma vez, escreva em qualquer lugar” - depois de aprender, você pode escrever para diferentes plataformas. É verdade que em cada plataforma você encontrará problemas específicos. Mas talvez isso seja apenas uma conseqüência da imaturidade do React Native - no momento, a versão estável mais recente é 0,61. Talvez com o lançamento da versão 1.0, a maioria desses problemas desapareça.

A abordagem Flutter é mais parecida com Escrever uma vez, compilar em qualquer lugar. E mesmo que a área de trabalho não esteja pronta para produção no momento, a web também está em alfa, mas tudo é necessário. E a capacidade de ter uma única base de código para todas as plataformas é um argumento forte.

É claro que o Flutter também não apresenta falhas, mas um pouco de experiência em usá-lo não me permite identificá-las. Portanto, se você quiser uma avaliação mais objetiva, fique à vontade para descontar o efeito da novidade.

Em geral, deve-se notar que Flutter deixou principalmente sentimentos positivos, embora ele tenha espaço para crescer. E no próximo projeto, eu estaria mais disposto a começar, e não no React Native.

O código fonte do projeto pode ser encontrado no GitHub .

PS Aproveito esta oportunidade para felicitar todos os envolvidos no dia do professor do passado.

Source: https://habr.com/ru/post/pt470251/


All Articles