Por que o Flutter está ganhando?

No ano passado, escrevi aplicativos Flutter para iOS e Android de qualquer maneira. Antes disso, eu tinha e tenho 5 anos de experiência com o Xamarin. Foram 5 anos maravilhosos. Graças a Xamarin e meu amor por essa estrutura, eu, em princípio, mudei para o campo de desenvolvedores, essa ferramenta me ajudou a ganhar muito dinheiro, conhecimento e encontrar colegas maravilhosos. Então, por que estou escrevendo no Flutter agora? Resposta curta, porque o Flutter cobre todas as necessidades do desenvolvimento de plataforma cruzada.


Um pouco de história


Corrija-me se eu estiver errado, mas 2009 foi, em muitos aspectos, a chave para o desenvolvimento móvel em geral e o desenvolvimento multiplataforma em particular. Em 2009, o iPhone 3gs foi lançado, o que lhe permitia executar aplicativos de terceiros na AppStore. Pela primeira vez, essa oportunidade apareceu um ano antes no iPhone 3G, mas o 3G tornou-se um iPhone verdadeiramente popular e "massivo". Novamente, um ano antes, em setembro de 2008, o Android foi apresentado ao público e em 2009 muitos fabricantes de celulares começaram a experimentar o Android para seus novos modelos de celulares. Na primavera de 2009, a Nitobi apresentou o PhoneGap, uma nova estrutura para a criação de aplicativos multiplataforma baseados em HTML5, CSS e JS. No mesmo ano, em setembroA Ximian lançou o MonoTouch, que permite escrever aplicativos iOS usando Mono e C #. No mesmo ano de 2009, em dezembro, a Rovio Entertainment lançou um jogo para iOS e, por um momento, Maemo, que de várias maneiras marcou o início da indústria de jogos para dispositivos móveis - Angry Birds. O último exemplo aqui não é acidental.

A primeira estrutura multiplataforma "para o povo" pode ser considerada PhoneGap (desenvolvedores de Qt, não jogue pedras). Foi uma ideia maravilhosa e muito óbvia - trazer a web para o mundo do desenvolvimento móvel. Em 2009, os recursos da Web já haviam começado a se estender além do navegador ( hello node.js), enquanto a criação de aplicativos da Web em JS era bastante direta. O segundo ponto, não menos importante, é a renderização da interface do usuário. A maneira como a renderização acontece está no mecanismo do navegador e todos esses mecanismos seguem mais ou menos os padrões W3C para HTML, CSS e DOM. Qualquer desenvolvedor web que criou um site espera que seu site pareça quaseidêntico em qualquer navegador, em qualquer plataforma. Este, na minha opinião, é o aspecto mais importante da web como uma plataforma aberta. Por que eu deveria aprender uma nova linguagem / estrutura para desenhar a interface do usuário para cada plataforma, se há muito tempo há um padrão para modelar a interface do usuário para diferentes navegadores.

Depois disso, Cordova se separou do PhoneGap e dele, do Ionic. Parece que essa é uma estrutura ideal, mas havia dois pontos: desempenho e integração do sistema operacional. Um dos principais objetivos ou, se você quiser, benchmarks de aplicativos, escritos em soluções de plataforma cruzada, era a "natividade" deles. Essa. Idealmente, 100% dos usuários devem considerar que seu aplicativo de plataforma cruzada é nativo. E isso significa que deve parecer nativo, funcionar como nativo e ter toda a integração possível com o sistema operacional. No começo, todos esses pontos para o PhoneGap eram inatingíveis, as capacidades dos smartphones há 10 anos não eram suficientes para a renderização da interface do usuário a 60 fps, a integração com o sistema operacional era mínima. Agora, existem algumas aplicações no Ionic que são difíceis de distinguir das nativas, mas imitar uma aplicação nativa ainda é uma tarefa.e não dado como tal. Vamos resumir um pouco. Escrever aplicativos da Web, ou melhor, aplicativos híbridos no iOS e Android, é possível e conveniente. É conveniente, porque o mecanismo de renderização da interface do usuário repousa inteiramente na plataforma WebView, além de existir uma camada de programadores já treinados e bem versados ​​na web.No entanto, em aplicativos híbridos, o desempenho e a integração do sistema operacional podem ser fracos.

Ao mesmo tempo que o PhoneGap, o MonoTouch foi lançado em 2009, que mais tarde foi renomeado para Xamarin.iOS. Além disso, no mesmo ano, o Titanium foi lançado, o que, por sua vez, também permitiu escrever aplicativos iOS em javascript. No início, o Titanium funcionava exatamente no mesmo paradigma do PhoneGap - contando com o WebView. Mas então eles adotaram a abordagem Xamarin. Qual é essa abordagem? Pode ser visto como algo no meio. A abordagem do Xamarin / Titanium / React.Native é que, em vez de tentar criar / migrar sua renderização de interface do usuário / existente, a estrutura simplesmente se integra ao nativo existente.

Em vez de desenhar um formulário em HTML, o Xamarin chama um elemento de interface do usuário nativo para isso (UITextField, TextEdit, etc). De fato, por que reinventar a roda? Todos os elementos de interface do usuário necessários existem nos SDKs e nos tempos de execução nativos; você só precisa aprender a se comunicar com eles a partir de suas VMs (mono, v8, etc.). Ao mesmo tempo, como você já adivinhou, você pode usar seu C #, JS, TS, F #, Kotlin etc. favorito e, ao mesmo tempo, o código que não interage diretamente com a interface do usuário é 100% multiplataforma. Você pode ir ainda mais longe. O mesmo UITextField e TextEdit são entidades conceitualmente idênticas, possuem propriedades e interfaces de interação semelhantes e, portanto, é possível criar uma entrada abstrata (olá Xamarin.Forms) e trabalhar apenas com ela, por raros ( não muito) exceção indo para o elemento da interface do usuário da plataforma. Não mencionei que, se sua vm puder trabalhar com a interface do usuário nativamente, provavelmente ela poderá chamar qualquer API de plataforma. Esta parece ser a opção perfeita. Interface do usuário nativa, desempenho nativo (oi Bridges no React.Native), 100% de integração do sistema operacional. Isso é realmente perfeito? Muito provavelmente - não, e o problema é que, na realidade, essas soluções não resolvem o problema do desenvolvimento de plataforma cruzada - uma única interface do usuário. Eles a disfarçam. Eu quero escrever uma vez, correr em todos os lugares. Isso está longe de ser o melhor lema para todos os tipos de programas e problemas, mas se encaixa bem na interface do usuário. Quero escrever a interface do usuário da mesma forma para todos, independentemente da plataforma. Por que um desenvolvedor da Web se permite usar HTML e CSS para escrever um site que será exibido da mesma maneira no Safari no iOS e Chrome no Android, mas nenhum desenvolvedor nativo?

De fato, os programadores escrevem há muito tempo uma interface do usuário de alto desempenho com uma base de código comum para iOS e Android. Esses programadores são chamados de desenvolvedores de jogos. Angry Birds foi escrito no mecanismo Cocos2d-x, Cuphead no Unity e Fortnite no Unreal Engine. Se os mecanismos de jogos puderem mostrar cenas de tirar o fôlego no telefone, os botões e as listas com animação suave serão definitivamente capazes. Então, por que ninguém os usa nesse sentido? A resposta é simples e banal, eles não se destinam a isso. Quando você abre o jogo, depende da lanterna a aparência da interface do usuário, quase nunca é necessário interagir com geolocalização, botões, câmera de vídeo etc. Enquanto você joga, você vive uma vida diferente em seu pequeno mundo, renderizada através do Canvas no seu UIViewController / Activity. Portantoos mecanismos de jogo têm uma integração relativamente ruim com o sistema operacional ; portanto, não existe (ou eu não vi) imitando o mecanismo superior da interface do usuário nativa.

Subtotais


Para uma estrutura ideal entre plataformas, precisamos:

  • Mapeamento da UI nativa
  • Desempenho nativo da interface do usuário
  • 100% de capacidade de chamar qualquer API do sistema operacional, como se fosse um aplicativo nativo

Agora você pensa que começarei a falhar sob Flutter, mas já ouço comentários irados: “Onde está o Qt!? Ele pode fazer tudo isso! De fato, Qt em um grau ou outro se encaixa nesses critérios. Embora eu duvide muito do primeiro deles. Mas o principal problema do Qt não é a dificuldade de escrever uma interface do usuário nativa, o principal problema é o C ++. Então já estou limpando meu rosto do espeto de codificadores de trabalho nas vantagens. Prós é uma faca suíça com esteróides anabolizantes; nos profissionais, você pode fazer tudo. Mas eu, como desenvolvedor front-end, não preciso de tudo isso. Preciso de uma linguagem simples e compreensível que funcione com interface do usuário e E / S. Então, aos nossos três pontos acima foi adicionado:

  • Fácil de aprender e linguagem bastante expressiva
  • Rantime que se encaixa bem no paradigma de desenvolvimento frontend

Bem, agora que destacamos algumas métricas de uma boa ferramenta de plataforma cruzada para o desenvolvimento de aplicativos móveis, podemos examinar cada uma delas e ver como ela é implementada no Flutter.

Mapeamento da UI nativa



Como descobrimos anteriormente, existem duas abordagens opostas ao trabalho com a interface do usuário em estruturas de plataforma cruzada. Esta é uma renderização da interface do usuário usando o WebView ou chamadas de elemento da interface do usuário nativas em cada plataforma. Cada abordagem tem vantagens e desvantagens. Mas eles não atendem a todas as necessidades dos desenvolvedores: parecem indistinguíveis da interface do usuário nativa + do desempenho nativo. O Flutter cobre todas essas necessidades com uma cabeça. A equipe do Flutter gastou uma certa quantidade de recursos na criação de elementos "nativos" na própria estrutura. Todos os widgets no Flutter são divididos em três grandes categorias:


Se você for para a seção cupertino, verá que esses widgets são indistinguíveis dos elementos nativos do iOS. Como desenvolvedor que usa o Flutter há algum tempo, posso confirmar que eles são indistinguíveis. Se você usa o CupertinoDatePicker, por exemplo, ao rolar, você sentirá exatamente o mesmo, um bom feedback do mecanismo Taptic / Haptic no seu iPhone como se fosse um elemento nativo do aplicativo nativo. Vou dizer mais, periodicamente eu abro o aplicativo realtor.com no site do meu iPhone e até recentemente eu não fazia ideia de que ele estava escrito em Flutter (ou em algo não nativo).

O Flutter não apenas permite que você use widgets "nativos" para 2 plataformas, mas também crie seus próprios, e é muito fácil! O paradigma inteiro é que tudo é widget funciona. Você pode criar elementos e animações da interface do usuário incrivelmente complexos em pouco tempo. Os encantos e a sabedoria da abordagem para trabalhar com a interface do usuário no Flutter foram recentemente descritos neste artigo no Habr, recomendo a leitura. Porque tudo isso funciona em um único mecanismo gráfico que processa tudo isso diretamente para cada plataforma (falaremos sobre isso mais adiante), você pode ter certeza de que tudo será exibido conforme o planejado.

Outro ponto bastante surpreendente. O Flutter suporta plataformas que começam com iOS 8 e Android API v16. Do ponto de vista da renderização da interface do usuário, o Flutter realmente não importa quais APIs estão disponíveis em uma plataforma específica. Ele teria a oportunidade de trabalhar com o Canvas e algum tipo de interação com o subsistema gráfico. E isso significa que podemos desenhar os elementos mais recentes da interface do AndroidX, por exemplo, em um telefone com 8 anos de idade. Certamente há uma questão de desempenho dessa abordagem nas plataformas suportadas mais antigas, mas essa é outra questão.

Desempenho nativo da interface do usuário



Como você pode ver, a abordagem de Flutter para renderização de interface do usuário é mais próxima da de aplicativos híbridos como o Ionic. Temos um único mecanismo para renderizar a interface do usuário em todas as plataformas, esta é a Skia Graphics Library. O Google comprou a Skia como um produto em 2005 e a transformou em um projeto de código aberto. Isso pelo menos sugere que este é um produto bastante maduro. Alguns recursos de desempenho do Skia:

  • Copiar na gravação para elementos gráficos e outros tipos de dados
  • Usando a pilha de memória sempre que possível para reduzir a fragmentação
  • Segurança de rosca, para melhor paralelização

Não achei testes de desempenho convincentes do Skia em comparação com bibliotecas semelhantes (consulte Cairo ), mas alguns testes mostram um ganho de desempenho de 50% em média, exceto em algumas situações específicas. Sim, isso não é particularmente importante, porque esses testes são baseados no uso do OpenGL em desktops e ... O

Skia pode interagir com muitos back-end da GPU. Desde recentesNo iOS, desde a versão 11, o Flutter usa o Metal como GPU de back-end por padrão. No Android, começando com a API 24 - Vulkan. Para versões abaixo - OpenGL. Tudo isso nos dá um ganho óbvio em produtividade. Em outras plataformas de "hardware", como eu o entendo, o Skia / Flutter usa o OpenGL, o que, em princípio, não nos impede de escrever aplicativos com desempenho gráfico suficiente.

Web se destaca. No momento, toda a renderização da interface do usuário ainda está no pacote Canvas / HTML. Portanto, a Skia não está envolvida nesse processo. Além disso, a Dart VM não interage diretamente com o DOM. Primeiro vem a conversão para js. Tudo isso não tem o melhor efeito sobre a produtividade e é diretamente visível a olho nu. No entanto, está em andamento o trabalho para implementar o CanvasKitno Flutter, que, por sua vez, permitirá que o Skia seja usado nos navegadores via WebGL.

Finalmente, os programadores de C # usam o SkiaSharp há um tempo relativamente longo - um invólucro sobre o Skia para Mono / .Net x. E a comunidade Xamarin usa essa biblioteca para desenhar elementos personalizados da interface do usuário e esta é uma biblioteca muito popular. Se isso não é uma vitória, então não sei o que é.

100% de capacidade de chamar qualquer SO da API


Em Flutter, existem 2 princípios de interação com o mundo "externo":


Os canais de plataforma permitem que você interaja com o runtime / API nativo por meio de um sistema de mensagens. Do ponto de vista arquitetônico, isso pode ser visto da seguinte maneira. Visualmente, o Flutter é apenas um Canvas, que é estendido para tela cheia no único Activity / UIViewController do seu aplicativo nativo. Essa é exatamente a mesma abordagem que uso desenvolvedores de jogos (mecanismos de jogos). Essa. Você pode abrir o projeto iOS / Android do seu aplicativo e adicionar qualquer outra funcionalidade ao Swift / Kotlin / etc. O problema é que o tempo de execução nativo e a VM Dart não sabem nada um do outro (além do fato de que o tempo de execução nativo saberá que o aplicativo possui Canvas e que algo é exibido lá). Além disso, se você, por exemplo, abrir o arquivo MainActivity.kt do seu projeto Android, verá algo parecido com isto:

class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
  }
}

Você percebeu que sua atividade é herdada de FlutterActivity? Isso nos dá a oportunidade de configurar o mecanismo para enviar mensagens diretamente ao Flutter / DartVM. Para fazer isso, precisamos substituir o método configureFlutterEnginee determinará o nome do método chamado e o nome do canal para o envio de mensagens assíncronas. Todos. Isso torna possível escrever qualquer código nativo e chamar qualquer API nativa! Ao mesmo tempo, já existe um grande número de plugins (pacotes) que o impedem de escrever código nativo; você pode usar apenas o Dart. Isso é maravilhoso! Você escreve a interface do usuário separadamente e uma vez para qualquer plataforma, usa o DartVM para trabalhar com interface do usuário, E / S e, como componente de computação, usa plug-ins que implementam recursos nativos e cobrem 99% de todas as funcionalidades. E se isso não for suficiente, você escreve nativamente e se comunica através do mecanismo de mensagens. História.

O segundo mecanismo é uma interface de função externa ou FFI. Este é um termo bastante comum para mecanismo de iterope com outros idiomas, principalmente C. No mundo .Net, esse mecanismo é chamado P / Invoke, para a JVM é JNI. Em resumo, essa é a capacidade de interagir com bibliotecas escritas em C / C ++ / etc. Na época do .Net Framework, por exemplo, não havia software escrito em C # e a grande maioria do software era escrita em C / C ++; portanto, era necessário um mecanismo para trabalhar com essas bibliotecas. O mesmo se aplica à JVM, Python, o nome dele. A FFI é de uma maneira ou de outra usada em todas as estruturas móveis de plataforma cruzada. Mais recentemente, o DartVM também começou a oferecer suporte ao FFI para interoperação com C e JavaScript! Embora esse recurso esteja em uma ramificação beta, mas já esteja disponível para uso (por seu próprio risco e risco).

Como você pode ver, o Flutter e o DartVM cobrem 100% das possibilidades em plataformas nativas e muito mais.

Fácil de aprender e linguagem bastante expressiva


Eu admito honestamente, enquanto o Dart para mim ainda não é a melhor linguagem do mundo. Não existe um sistema de tipo estrito, não há bolos funcionais, como recursos de Correspondência de padrões ou Imutabilidade (como eles serão entregues em breve), etc. Sobre o sistema de tipos, o Dart foi originalmente concebido como uma linguagem “sem uma típica”, ala JS, mas, para suporte normal à compilação da AOT, era necessário, no entanto, levar o sistema de tipos a um sistema mais rígido, embora não completamente, eu diria. Ainda me incomoda trabalhar com assinaturas de métodos, ou seja, com argumentos. Todos esses colchetes, @requiredpor algum motivo , estão enfurecidos . Mas o dardo é uma linguagem muito fácil de aprender. Na sintaxe, este é um cruzamento entre Java e JS para mim. Dart perdoa muito, como JS. Em geral, este é um idioma bastante fácil de aprender, não tive problemas significativos.

Rantime que se encaixa bem no paradigma de desenvolvimento frontend


Agora vamos falar sobre o Dart VM. Em geral, o Dart VM inclui muitas coisas, do GC ao Profiler e Observatory. Aqui eu quero falar apenas sobre GC e tempo de execução condicional. Você pode se familiarizar com o funcionamento do tempo de execução e em que consiste aqui . Não sou especialista neste campo, mas observei algumas das vantagens do Dart VM, que tentarei descrever. Antes disso, gostaria de observar que o Dart e a VM correspondente foram desenvolvidos inicialmente como um substituto para o JS, o que, por assim dizer, sugere o foco no desenvolvimento de front-end.

Isolados

O Dart VM possui o conceito Isolar. Isolar é uma combinação de um thread principal que é executado diretamente no código Dart e no heap isolado, onde os objetos do código Dart são realmente alocados. Esta é uma estrutura simplificada. O Isolate também possui threads auxiliares / do sistema, existem threads do SO que podem entrar e sair do Isolate etc. A pilha também está presente no Isolate, mas você, como usuário, não opera nela. A principal coisa que precisa ser enfatizada aqui é que, se você observar um Isolate, esse será um ambiente de thread único. Por padrão, o Flutter usa um padrão isolado. Não se parece com nada? Sim, este é o ambiente JS. Assim como no JS, os programadores do Dart não podem trabalhar com multithreading. Alguém pode pensar que isso é uma bagunça, simplificação e violação dos direitos de desenvolvedores reais, mas acho que, ao trabalhar com a interface do usuário,quando você opera com um DOM condicional (e não desenha polígonos na tela), não é necessário, é perigoso operar com vários encadeamentos.

Aqui, é claro, eu fui astuto, se você realmente quiser, pode usar o Isolate iniciado separadamente para executar tarefas paralelas (Olá WebWorkers). Aqui você pode ver em detalhes como pode trabalhar com o Isolate in Flutter adicional. Em geral, os isolados, como o nome indica, não sabem nada um do outro, não mantêm links entre si e se comunicam através de um sistema de mensagens.

Além da abordagem de thread único, o fato de que um heap separado é alocado para cada Isolate sem a capacidade de manipular a pilha desse thread é, na minha opinião, uma abordagem muito boa. Se você está escrevendo um aplicativo de servidor que manipula um grande número de linhas, por exemplo, e essas linhas são armazenadas em uma pilha, onde elas aparecem e desaparecem a uma velocidade tremenda, fragmentando a memória e adicionando trabalhos de GC, qualquer maneira de transferir essas linhas, ou pelo menos parte, de pilhas na pilha economizarão recursos e melhorarão o desempenho. Um exemplo é mais ou menos, mas você me entende. Porém, ao trabalhar com a interface do usuário, em que existe um número suficiente de elementos da interface do usuário que podem ter uma vida útil curta (por exemplo, animação), mas apenas um cliente e a quantidade de dados processados ​​são desprezíveis em comparação ao aplicativo do servidor,a capacidade de trabalhar diretamente com a pilha é simplesmente desnecessária. Não estou falando de boxe / unboxing, o que poderia ser neste caso e que é absolutamente inútil. E deve-se observar que os objetos no Dart VM são alocados com bastante frequência. Mesmo para gerar a quantidade dupla do método Dart, a VM aloca separadamente uma parte no heap. Como o GC lida com essa carga? Vamos dar uma olhada.

Young Space Scavenger (e varredura de marca paralela)

Primeiro, como todos os GCs, o GC na Dart VM tem gerações. Além disso, o GC na Dart VM pode ser dividido de acordo com o princípio do trabalho em 2 componentes: Young Space Scavenger e Parallel Mark Sweep. Não vou me debruçar sobre o último princípio, esse é um princípio bastante popular de limpeza de memória, que é implementado em quase todos os lugares e não oferece ao Flutter uma vantagem especial. Estamos interessados ​​no primeiro. O princípio de funcionamento do Young Space Scavenger está bem ilustrado na figura a seguir:


Isso demonstra claramente as vantagens dessa abordagem. O Young Space Scavenger trabalha para os objetos mais recentes da memória, podemos dizer que para a primeira / zero geração de objetos. Freqüentemente, e isso é característico da VM Flutter / Dart, a maioria dos novos objetos tem vida útil curta. Em uma situação em que você aloca muitos objetos que não duram muito, a memória pode ser muito fragmentada. Nesse caso, você terá que gastar tempo na memória ou no processador para corrigir o problema (embora não deva resolver o problema com esses métodos). O Young Space Scavenger resolve esse problema. Se você observar a figura acima, realmente não há 6 etapas, não é necessário limpar o primeiro bloco de memória; por padrão, achamos que esse bloco está vazio depois de copiar objetos para o segundo. Bem, ao copiar objetos sobreviventes para o segundo pedaço,nós naturalmente os definimos um por um sem criar fragmentação. Tudo isso permite que a VM aloque muitos novos objetos a um preço bastante baixo.

GC de tempo ocioso

Como você entende, as equipes de VM Flutter e Dart trabalham juntas e o resultado dessa cooperação pode ser considerado o GC de tempo ocioso. Como o nome indica, isso é coleta de lixo no momento em que nada acontece. No contexto do Flutter, no momento em que o aplicativo visualmente não altera nada. Não há animação, rolagem ou interação do usuário. Nesses momentos, o Flutter envia mensagens para a Dart VM que agora, em princípio, é um bom momento para iniciar a coleta de lixo. Em seguida, o coletor de lixo decide se deve iniciar seu trabalho. Obviamente, a coleta de lixo nesse sentido ocorre para objetos mais antigos que são gerenciados por meio da Varredura de Marca Paralela, que por si só é um processo bastante caro e o Idle Time GC é um mecanismo muito útil nesse sentido.

Há outras coisas comoCompactação deslizante e ponteiros compactados . O primeiro é o mecanismo de desfragmentação de memória após executar a varredura de marca paralela. Esse também é um processo caro e só funciona se houver tempo ocioso. A segunda abordagem, Ponteiros compactados, compacta ponteiros de 64 bits em 32 bits, o que economiza memória (acho que isso é muito mais útil em um ambiente de servidor do que em um móvel).

Sumário


Se você ler esta linha, primeiro, parabéns e, em segundo lugar, devo dizer que não tenho experiência em escrever artigos; portanto, não entendo se consegui entender o que quero dizer. E a idéia é simples: quando você escreve um aplicativo móvel com o Flutter, ele é nativo. E na forma de um bônus, você obtém uma velocidade de desenvolvimento de aplicativos bastante decente. Recarregar / Reiniciar a quente é simplesmente uma coisa indispensável no desenvolvimento do Frontend. Você pode imaginar algum tipógrafo que precisaria criar / compilar o projeto inteiro para cada navegador, por exemplo, com cada mudança de cor de um botão? Claro que não. Em geral, o Hot Reload / Restart merece um artigo separado. Mas eu estava distraído.

Minha experiência com Flutter me diz que essa estrutura será dominante no futuro próximo. Periodicamente, passo entrevistas para uma posição de desenvolvedor do Flutter e, na metade dos casos, as empresas que procuram um desenvolvedor do Flutter têm uma equipe de desenvolvedores nativos móveis. Eles apenas tentaram o Flutter em projetos de interiores / paralelos, ficaram satisfeitos / encantados e estavam lentamente se mudando para o Flutter. Esta é uma vitória real, parece-me. O que não se pode dizer sobre o Xamarin, infelizmente. Muitas vezes, a decisão de escolher o Xamarin se deve simplesmente ao fato de o restante da pilha ser gravado em .Net, e essa é uma inclinação escorregadia.

Para resumir, quero dizer que, se você estiver pensando em qual lado se aproximar ao desenvolver seu novo aplicativo móvel, veja Flutter.

All Articles