Vamos finalizar a aplicação codificando o controlador e a UI.
Antes, vamos instalar um complemento no VSC para ganhar produtividade. Trata-se do flutter_mobx.
Além de snnipets para criar classes, ele possui uma ferramenta que executa o comando: flutter pub run build_runner para geração de classes do Mobx.
Separando a lógica
Na pasta pages vamos criar o arquivo home_controller.dart.
Com a extensão acima instalada, podemos digitar mobx e enter para que seja criado uma classe MobxX Store.
Codificaremos assim:
import 'package:flutter/material.dart';
import 'package:flutter_covid19/models/mundo_model.dart';
import 'package:flutter_covid19/models/pais_model.dart';
import 'package:flutter_covid19/respositories/covidapi_repository.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:mobx/mobx.dart';
part 'home_controller.g.dart';
class HomeController = _HomeControllerBase with _$HomeController;
abstract class _HomeControllerBase with Store {
final api = Modular.get<CovidApiRepository>();
_HomeControllerBase() {
getInfoMundo();
getInfoPais();
}
@observable
int currentIndex = 0;
@observable
TextEditingController textFieldController = TextEditingController();
@observable
Mundo mundo;
@observable
Pais pais;
@action
getInfoMundo() async {
mundo = await api.getMundo();
}
@action
getInfoPais({String nomePais = 'brazil'}) async {
try{ pais = await api.getPais(pais: nomePais);}
catch(exception){
return Container();
}
}
@action
changePage(int index) {
currentIndex = index;
}
}
Comentando o código
Devemos recuperar a classe que faz as chamadas a api. Utilizamos o modular em Modular.get<CovidApiRepository>.
No construtor vamos invocar dois métodos que serão implementados em seguida, getInfoMundo() e getInfoPais()
_HomeControllerBase() {
getInfoMundo();
getInfoPais();
}
Declaramos quatro objetos. Os que representam os nossos modelos (parte1) mundo e o país, um para controlar o visão na aba (currentIndex) além de um textFieldController para realizarmos a consulta.
Todos estão com a anotação @observable indicando que queremos ser notificados do seu estado.
A IDE apresentará alguns erros até esse momento, pois ainda não estamos fazendo uso das bibliotecas instaladas para geração automática. Para corrigir isso, vamos usar a extensão mais uma vez para executar o comando.
Encontre e clique no rodapé do editor o texto build_runner watch e aguarde um momento. Será executado um script para geração de classes.
Note que será criada a classe home_controller.g.dart.
Não edite essa classe. Toda vez que você alterar o Store essa classe será gerada/atualizada automaticamente.
A implementação do getInfoMundo e getInfoPais são chamadas aos métodos que implementamos na parte 1.
Para os métodos usamos outro tipo de anotação, @action, que basicamente modifica os observables. Assim, os observadores são notificados somente após a conclusão atômica da ação.
A diferença está na passagem de argumentos do getPais que definimos um valor padrão.
@action
getInfoPais({String nomePais = 'brazil'}) async {
try {
pais = await api.getPais(pais: nomePais);
} catch (exception) {
return Container();
}
}
Finalmente codificamos um método para controlar a view da barra de navegação.
@action
changePage(int index) {
currentIndex = index;
}
HOME UI
Vamos finalizar escrevendo a interface visual.
Ainda na pasta pages criaremos o arquivo home_page.dart
Temos inicialmente a assinatura da classe HomePage estendendo um StatefulWidget
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
Em seguida, criamos o estado com a seguinte assinatura:
class _HomePageState extends ModularState<HomePage, HomeController>
Utilizando o modular podemos usar o ModularState, indicando qual é o controlador.
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
return Scaffold(
backgroundColor: Colors.white,
body: (this.controller.mundo != null && this.controller.pais != null)
? _buildBody()
: Center(child: CircularProgressIndicator()),
bottomNavigationBar: _buildBottomBar(),
);
});
}
No build temos o primeiro widget Observer (pacote flutter_mobx). Toda vez que esses observáveis mudarem, o Observer reconstrói e renderiza.
Para acessar as propriedades do controlador (HomeController) invocamos o controller.
No body verificamos se os objetos mundo e país estão nulos para decidir a renderização do corpo (_buidBody) ou um widget de espera (CircularProgressIndicator).
Widget _buildBody() {
return (this.controller.currentIndex == 0)
? _buildBodyMundo()
: _buildBodyPais();
}
Controlamos o índice para alternar entre o Mundo e o País. O índice é passado no método _buildBottomBar() em currentIndex.
Widget _buildBottomBar() {
return BottomNavigationBar(
selectedItemColor: Colors.grey.shade800,
unselectedItemColor: Colors.grey,
currentIndex: this.controller.currentIndex,
onTap: this.controller.changePage,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.group_work),
title: Text("Mundo"),
),
BottomNavigationBarItem(
icon: Icon(Icons.flag),
title: Text("Pais"),
),
],
);
}
Ao longo da classe criamos outros métodos para padronizar a visualização das informações (ver _buildRow, _buildHeader e _buildTile). A prática mais recomendada seria separar em outros widgets para reutilização em outras partes do projeto, se fosse o caso.
No método _buildBodyPais acessamos os valores dos atributos passando no parâmetro dos métodos acima.
Exemplos: controller.pais.nome, controller.pais.urlBandeira …
Widget _buildBodyPais() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_buildHeader(controller.pais.nome,
isPais: true, url: controller.pais.urlBandeira),
const SizedBox(height: 20.0),
_buildRow(Colors.brown, Icons.person_outline, "Total de Infectados",
controller.pais.casos.toString()),
const SizedBox(height: 16.0),
_buildRow(Colors.red, Icons.person_pin, "Mortes",
controller.pais.mortes.toString()),
const SizedBox(height: 16.0),
_buildRow(Colors.green, Icons.person_add, "Recuperados",
controller.pais.recuperados.toString()),
],
),
);
}
Finalmente, temos um método que desenha um alert para inserir o nome do país:
_displayDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Situação de outro País'),
content: TextField(
controller: controller.textFieldController,
decoration: InputDecoration(hintText: "INFORME O PAÍS:"),
),
actions: <Widget>[
RaisedButton(
onPressed: () {
this.controller.getInfoPais(
nomePais:
this.controller.textFieldController.text.toString());
Modular.to.pop();
},
child: Text(
"OK",
style: TextStyle(color: Colors.white),
),
color: const Color(0xFF1BC0C5),
),
new FlatButton(
child: new Text('CANCELAR'),
onPressed: () {
Modular.to.pop();
},
)
],
);
});
}
Vinculamos um TextField ao controlador e no onPressed no botão passamos o texto no método getInfoPais.
Bem provável que deixei passar algumas informações da implementação, mas se tiver alguma dúvida, deixe nos comentários.
Se você leu até aqui e gostou, dê um clap!