Desenvolvimento Web e Desktop Responsivo com Flutter

Publicados: 2022-03-10
Resumo rápido ↬ O Flutter já causou bastante impacto no cenário de desenvolvimento móvel. Agora também está assumindo dispositivos maiores. Aqui está o que você precisa saber para estar pronto para assumir a tarefa de desenvolver aplicativos web e desktop usando esta maravilhosa estrutura multiplataforma.

Este tutorial não é uma introdução ao próprio Flutter. Existem muitos artigos, vídeos e vários livros disponíveis on-line com introduções simples que ajudarão você a aprender o básico do Flutter. Em vez disso, abordaremos os dois objetivos a seguir:

  1. O estado atual do desenvolvimento não móvel do Flutter e como você pode executar o código Flutter no navegador, em um computador desktop ou laptop;
  2. Como criar aplicativos responsivos usando o Flutter, para que você possa ver seu poder - especialmente como uma estrutura da Web - em exibição completa, terminando com uma nota sobre roteamento baseado em URL.

Vamos entrar nisso!

O que é Flutter, por que é importante, no que evoluiu, para onde está indo

Flutter é a mais recente estrutura de desenvolvimento de aplicativos do Google. O Google prevê que seja abrangente: permitirá que o mesmo código seja executado em smartphones de todas as marcas, em tablets e em computadores desktop e laptops como aplicativos nativos ou como páginas da web.

É um projeto muito ambicioso, mas o Google tem sido incrivelmente bem-sucedido até agora, particularmente em dois aspectos: na criação de uma estrutura verdadeiramente independente de plataforma para aplicativos nativos Android e iOS que funciona muito bem e está totalmente pronta para uso em produção e na criação de uma frente impressionante -end framework web que pode compartilhar 100% do código com um aplicativo Flutter compatível.

Na próxima seção, veremos o que torna o aplicativo compatível e qual é o estado do desenvolvimento do Flutter não móvel a partir de agora.

Mais depois do salto! Continue lendo abaixo ↓

Desenvolvimento não móvel com Flutter

O desenvolvimento não móvel com o Flutter foi divulgado pela primeira vez de maneira significativa no Google I/O 2019. Esta seção é sobre como fazê-lo funcionar e sobre quando funciona.

Como habilitar o desenvolvimento da Web e da área de trabalho

Para habilitar o desenvolvimento web, primeiro você deve estar no canal beta do Flutter. Há duas maneiras de chegar a esse ponto:

  • Instale o Flutter diretamente no canal beta baixando a versão beta mais recente apropriada do arquivo SDK.
  • Se você já tem o Flutter instalado, mude para o canal beta com $ flutter channel beta , e então execute a troca atualizando sua versão do Flutter (que na verdade é um git pull na pasta de instalação do Flutter) com $ flutter upgrade .

Depois disso, você pode executar isso:

 $ flutter config --enable-web

O suporte de desktop é muito mais experimental, especialmente devido à falta de ferramentas para Linux e Windows, tornando o desenvolvimento de plugins especialmente uma grande dor, e devido ao fato de que as APIs usadas para isso são destinadas ao uso de prova de conceito e não para Produção. Isso é diferente do desenvolvimento da Web, que usa o compilador dart2js testado e comprovado para compilações de lançamento, que nem são compatíveis com aplicativos de desktop nativos do Windows e Linux.

Nota : O suporte para macOS é um pouco melhor do que o suporte para Windows e Linux, mas ainda não é tão bom quanto o suporte para a Web e não é tão bom quanto o suporte completo para plataformas móveis.

Para habilitar o suporte para desenvolvimento de desktop, você precisa alternar para o canal de lançamento master seguindo as mesmas etapas descritas anteriormente para o canal beta . Em seguida, execute o seguinte substituindo <OS_NAME> por linux , windows ou macos :

 $ flutter config --enable-<OS_NAME>-desktop

Neste ponto, se você tiver problemas com qualquer uma das etapas a seguir que descreverei porque a ferramenta Flutter não está fazendo o que estou dizendo que deveria fazer, algumas etapas comuns de solução de problemas são estas:

  • Execute o flutter doctor para verificar se há problemas. Um efeito colateral deste comando Flutter é que ele deve baixar todas as ferramentas necessárias que não possui.
  • Execute flutter upgrade .
  • Desligue e ligue novamente. A antiga resposta de suporte técnico de nível 1 de reiniciar o computador pode ser exatamente o que é necessário para que você possa aproveitar todas as riquezas do Flutter.

Executando e criando aplicativos da Web Flutter

O suporte à web do Flutter não é ruim, e isso se reflete na facilidade de desenvolvimento para a web.

Executando isso…

 $ flutter devices

… deve mostrar imediatamente uma entrada para algo assim:

 Web Server • web-server • web-javascript • Flutter Tools

Além disso, a execução do navegador Chrome deve fazer com que o Flutter também mostre uma entrada para ele. A execução do flutter run em um projeto Flutter compatível (mais sobre isso posteriormente) quando o único “dispositivo conectado” que aparece é o servidor web fará com que o Flutter inicie um servidor web em localhost:<RANDOM_PORT> , que permitirá que você acesse seu Flutter aplicativo da web de qualquer navegador.

Se você instalou o Chrome, mas não está aparecendo, você precisa definir a variável de ambiente CHROME_EXECUTABLE para o caminho para o arquivo executável do Chrome.

Executando e construindo aplicativos de desktop Flutter

Depois de habilitar o suporte para desktop Flutter, você pode executar um aplicativo Flutter nativamente em sua estação de trabalho de desenvolvimento com flutter run -d <OS_NAME> , substituindo <OS_NAME> pelo mesmo valor usado ao habilitar o suporte para desktop. Você também pode compilar binários no diretório de build com flutter build <OS_NAME> .

Antes que você possa fazer isso, você precisa ter um diretório contendo o que o Flutter precisa construir para sua plataforma. Isso será criado automaticamente quando você criar um novo projeto, mas você precisará criá-lo para um projeto existente com flutter create . . Além disso, as APIs do Linux e do Windows são instáveis, portanto, talvez seja necessário regenerá-las para essas plataformas se o aplicativo parar de funcionar após uma atualização do Flutter.

Quando um aplicativo é compatível?

O que eu quis dizer o tempo todo ao mencionar que um aplicativo Flutter precisa ser um “projeto compatível” para funcionar na área de trabalho ou na web? Simplificando, quero dizer que ele não deve usar nenhum plug-in que não tenha uma implementação específica de plataforma para a plataforma na qual você está tentando construir.

Para deixar esse ponto absolutamente claro para todos e evitar mal-entendidos, observe que um plug-in Flutter é um pacote específico do Flutter que contém código específico da plataforma necessário para fornecer seus recursos.

Por exemplo, você pode usar o pacote url_launcher desenvolvido pelo Google o quanto quiser (e talvez queira, já que a web é construída em hiperlinks).

Um exemplo de pacote desenvolvido pelo Google cujo uso impediria o desenvolvimento da Web é path_provider , que é usado para obter o caminho de armazenamento local para salvar arquivos. Este é um exemplo de um pacote que, aliás, não é útil para um aplicativo da web, então não poder usá-lo não é realmente uma chatice, exceto pelo fato de que você precisa alterar seu código para para funcionar na web se você estiver usando.

Por exemplo, você pode usar o pacote shared_preferences, que depende do HTML localStorage na web.

Advertências semelhantes são válidas em relação às plataformas de desktop: muito poucos plugins são compatíveis com plataformas de desktop e, como esse é um tema recorrente, muito mais trabalho precisa ser feito no lado da área de trabalho do que o realmente necessário no Flutter para a web.

Criando layouts responsivos no Flutter

Por causa do que descrevi acima e para simplificar, vou assumir no restante deste post que sua plataforma de destino é a web, mas os conceitos básicos também se aplicam ao desenvolvimento de desktop.

Apoiar a web tem benefícios e responsabilidades. Ser praticamente forçado a oferecer suporte a diferentes tamanhos de tela pode parecer uma desvantagem, mas considere que a execução do aplicativo nos navegadores da Web permite que você veja com muita facilidade como seu aplicativo ficará em telas de diferentes tamanhos e proporções, sem precisar executar separadamente emuladores de dispositivos móveis.

Agora, vamos falar de código. Como tornar seu aplicativo responsivo?

Existem duas perspectivas a partir das quais esta análise é feita:

  1. “Quais widgets estou usando ou posso usar que podem ou devem se adaptar a telas de diferentes tamanhos?”
  2. “Como posso obter informações sobre o tamanho da tela e como posso usá-las ao escrever o código da interface do usuário?”

Responderemos a primeira pergunta mais tarde. Vamos primeiro falar sobre o último, porque ele pode ser tratado com muita facilidade e está no centro da questão. Existem duas maneiras de fazer isso:

  1. Uma maneira é pegar as informações do MediaQueryData da raiz MediaQuery InheritedWidget , que precisa existir na árvore de widgets para que um aplicativo Flutter funcione (é parte de MaterialApp/WidgetsApp/CupertinoApp ), que você pode obter, assim como qualquer outro InheritedWidget , com MediaQuery.of(context) , que possui uma propriedade size , que é do tipo Size e que, portanto, possui duas propriedades width e height do tipo double .
  2. A outra maneira é usar um LayoutBuilder , que é um widget construtor (assim como um StreamBuilder ou um FutureBuilder ) que passa para a função builder (junto com o context ) um objeto BoxConstraints que possui as propriedades minHeight , maxHeight , minWidth e maxWidth .

Aqui está um exemplo de DartPad usando o MediaQuery para obter restrições, cujo código é o seguinte:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${MediaQuery.of(context).size.width}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${MediaQuery.of(context).size.height}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ); }

E aqui está um usando o LayoutBuilder para a mesma coisa:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${constraints.maxWidth}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${constraints.maxHeight}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ) ); }

Agora, vamos pensar em quais widgets podem se adaptar às restrições.

Em primeiro lugar, vamos pensar nas diferentes maneiras de dispor vários widgets de acordo com o tamanho da tela.

O widget que se adapta mais facilmente é o GridView . Na verdade, um GridView construído usando o construtor GridView.extent nem precisa do seu envolvimento para ser responsivo, como você pode ver neste exemplo muito simples:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); } import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }

Você pode acomodar conteúdo de tamanhos diferentes alterando o maxCrossAxisExtent .

Esse exemplo serviu principalmente para mostrar a existência do construtor GridView.extent GridView , mas uma maneira muito mais inteligente de fazer isso seria usar um GridView.builder com um SliverGridDelegateWithMaxCrossAxisExtent , neste caso onde os widgets a serem mostrados na grade são criados dinamicamente a partir de outra estrutura de dados, como você pode ver neste exemplo:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => Scaffold( body: GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ) ); }

Um exemplo de GridView se adaptando a diferentes telas é minha página de destino pessoal, que é um aplicativo web Flutter muito simples que consiste em um GridView com um monte de Cards , assim como o código de exemplo anterior, exceto que os Cards são um pouco mais complexos e maiores .

Uma mudança muito simples que poderia ser feita em aplicativos projetados para telefones seria substituir uma Drawer por um menu permanente à esquerda quando houver espaço.

Por exemplo, poderíamos ter um ListView de widgets, como o seguinte, que é usado para navegação:

 class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); }

Em um smartphone, um lugar comum para usar seria dentro de uma Drawer (também conhecida como menu de hambúrguer).

Alternativas para isso seriam o BottomNavigationBar ou o TabBar , em combinação com o TabBarView , mas com ambos teríamos que fazer mais alterações do que o necessário com a gaveta, então vamos ficar com a gaveta.

Para mostrar apenas o Drawer contendo o Menu que vimos anteriormente em telas menores, você escreveria um código parecido com o seguinte snippet, verificando a largura usando o MediaQuery.of(context) e passando um objeto Drawer para o Scaffold somente se for menos do que algum valor de largura que acreditamos ser apropriado para nosso aplicativo:

 Scaffold( appBar: AppBar(/* ... \*/), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: /* ... \*/ )

Agora, vamos pensar no body do Scaffold . Como amostra de conteúdo principal do nosso aplicativo, usaremos o GridView que construímos anteriormente, que mantemos em um widget separado chamado Content para evitar confusão:

 class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); } class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Em telas maiores, o próprio corpo pode ser uma Row que mostra dois widgets: o Menu , restrito a uma largura fixa, e o Content preenchendo o restante da tela.

Em telas menores, todo o body seria o Content .

Vamos envolver tudo em um SafeArea e Center porque às vezes os widgets de aplicativos da web Flutter, especialmente ao usar Row se Column s, acabam fora da área visível da tela, e isso é corrigido com SafeArea e/ou Center .

Isso significa que o body do Scaffold será o seguinte:

 SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) )

Aqui está tudo isso junto:

(Visualização grande)
 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: HomePage() ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Essa é a maioria das coisas que você precisa como introdução geral à interface do usuário responsiva no Flutter. Grande parte de sua aplicação dependerá da interface do usuário específica do seu aplicativo, e é difícil identificar exatamente o que você pode fazer para tornar seu aplicativo responsivo, e você pode adotar muitas abordagens, dependendo de sua preferência. Agora, porém, vamos ver como podemos fazer um exemplo mais completo em um aplicativo responsivo, pensando em elementos comuns de aplicativo e fluxos de interface do usuário.

Colocando em contexto: tornando um aplicativo responsivo

Até agora, temos apenas uma tela. Vamos expandir isso em um aplicativo de duas telas com navegação baseada em URL!

Criando uma página de login responsiva

É provável que seu aplicativo tenha uma página de login. Como podemos tornar isso responsivo?

As telas de login em dispositivos móveis geralmente são bastante semelhantes entre si. O espaço disponível não é muito; normalmente é apenas uma Column com algum Padding ao redor de seus widgets, e contém TextField s para digitar um nome de usuário e uma senha e um botão para fazer login. , um TextEditingController para cada TextField ) página de login para um aplicativo móvel pode ser o seguinte:

 Scaffold( body: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () {} ) ] ), ), )

Parece bom em um dispositivo móvel, mas esses TextField muito amplos começam a parecer chocantes em um tablet, quanto mais em uma tela maior. No entanto, não podemos simplesmente decidir sobre uma largura fixa porque os telefones têm tamanhos de tela diferentes e devemos manter um certo grau de flexibilidade.

Por exemplo, por meio de experimentação, podemos descobrir que a largura máxima deve ser 500. Bem, definiríamos as constraints do Container para 500 (usei um Container em vez de Padding no exemplo anterior porque sabia onde estava indo com isso ) e estamos prontos, certo? Na verdade não, porque isso faria com que os widgets de login ficassem no lado esquerdo da tela, o que pode ser ainda pior do que esticar tudo. Então, envolvemos um widget Center , assim:

 Center( child: Container( constraints: BoxConstraints(maxWidth: 500), padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column(/* ... \*/) ) )

Isso já parece bom, e nem tivemos que usar um LayoutBuilder ou o MediaQuery.of(context).size . Vamos dar um passo adiante para fazer isso parecer muito bom, no entanto. Ficaria melhor, na minha opinião, se a parte do primeiro plano fosse de alguma forma separada do plano de fundo. Podemos conseguir isso dando uma cor de fundo ao que está por trás do Container com os widgets de entrada e mantendo o Container de primeiro plano em branco. Para torná-lo um pouco melhor, vamos evitar que o Container se estenda para a parte superior e inferior da tela em dispositivos grandes, dar-lhe cantos arredondados e dar-lhe uma boa transição animada entre os dois layouts.

Tudo isso agora requer um LayoutBuilder e um Container externo para definir uma cor de fundo e adicionar preenchimento em todo o Container e não apenas nas laterais apenas em telas maiores. Além disso, para tornar animada a alteração na quantidade de preenchimento, precisamos apenas transformar esse Container externo em um AnimatedContainer , o que requer uma duration para a animação, que definiremos como meio segundo, que é Duration(milliseconds: 500) em código.

Aqui está o exemplo de uma página de login responsiva:

(Visualização grande)
 class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0), child: Center( child: Container( padding: EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomePage() ) ); } ) ] ), ), ) ); } ) ); }

Como você pode ver, também alterei o RaisedButton do onPressed para um retorno de chamada que nos leva a uma tela chamada HomePage (que pode ser, por exemplo, a visualização que construímos anteriormente com um GridView e um menu ou uma gaveta). Agora, porém, essa parte de navegação é o que vamos focar.

Rotas nomeadas: tornando a navegação do seu aplicativo mais parecida com um aplicativo da Web adequado

Uma coisa comum para os aplicativos da Web é a capacidade de alterar as telas com base na URL. Por exemplo, https://appurl/login deve fornecer algo diferente de https://appurl/somethingelse . O Flutter, na verdade, suporta rotas nomeadas , que têm dois propósitos:

  1. Em um aplicativo da web, eles têm exatamente esse recurso que mencionei na frase anterior.
  2. Em qualquer aplicativo, eles permitem que você predefina rotas para seu aplicativo e dê nomes a eles e, em seguida, navegue até eles apenas especificando seus nomes.

Para fazer isso, precisamos alterar o construtor MaterialApp para um que se pareça com o seguinte:

 MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } );

E então podemos mudar para uma rota diferente usando Navigator.pushNamed(context, routeName) e Navigator.pushReplacementNamed(context, routeName) , em vez de Navigator.push(context, route) e Navigator.pushReplacement(context, route) .

Aqui está o aplicado ao aplicativo hipotético que construímos no restante deste artigo. Você realmente não pode ver as rotas nomeadas em ação no DartPad, então você deve tentar isso em sua própria máquina com flutter run ou verificar o exemplo em ação:

(Visualização grande)
 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } ); } class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0), child: Center( child: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacementNamed( context, "/home" ); } ) ] ), ), ) ); } ) ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ), FlatButton( onPressed: () {Navigator.pushReplacementNamed( context, "/login");}, child: ListTile( leading: Icon(Icons.exit_to_app), title: Text("Log Out"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Avante Com Sua Aventura Flutter

Isso deve dar uma ideia do que você pode fazer com o Flutter em telas maiores, especificamente na web. É uma estrutura adorável, muito fácil de usar, e seu suporte multiplataforma extremo só torna mais essencial aprender e começar a usar. Então, vá em frente e comece a confiar no Flutter para aplicativos da web também!

Recursos adicionais

  • “Conchas de desktop”, GitHub
    O estado atual e sempre atualizado do Flutter na área de trabalho
  • “Suporte de desktop para Flutter”, Flutter
    Informações sobre as plataformas de desktop totalmente suportadas
  • “Suporte da Web para Flutter”, Flutter
    Informações sobre Flutter para a web
  • “Todas as Amostras”, Flutter
    Uma lista com curadoria de amostras e aplicativos do Flutter