Desenvolvendo um Aplicativo Simples com Flutter – Conversor de Binário para Decimal
Desenvolvimento de aplicativos é sempre um desafio, principalmente quando se é iniciante… Bom não é bem o meu caso já que sou um amante de tecnologia e trabalho na área de desenvolvimento de software, devido a isso estou sempre em busca de novas tecnologias com o intuito de experimentar e ver até onde consigo chegar em uma determinada tecnologia sem possuir domínio sobre ela.
Pensando em desafios me deparei com um repositório no github simplesmente genial, app-ideas, onde é possível encontrar diversos desafios indo desde o básico até o avançado contemplando até mesmo “user cases“!
Comprei a briga de desenvolver o primeiro desafio proposto, Bin2Dec, utilizando o Flutter e cá estou para apresentar o resultado, com o intuito de até mesmo auxiliar quem esteja em busca de material para desenvolvimento de aplicativos com o Flutter.
Primeiro passo
Entender o desafio proposto!
Ao acessar a página do desafio, Bin2Dec, podemos perceber que ele é bastante rico em detalhes, sim é em inglês, caso possua dificuldade com tal linguagem, recomendo ler a primeira vez em inglês mesmo, e em seguida não vejo mal algum em utilizar alguma ferramenta de tradução como o próprio Google Translate para se inteirar do desafio.
De forma resumida o desafio consiste em:
1 – Permitir que o usuário possa informar valores binários (0 ou 1).
2 – O usuário deve ser notificado caso ele informe algum valor diferente de um valor binário (0 ou 1). No meu exemplo eu decidi por não notificar e não permitir a entrada de valores que não sejam binários (0 ou 1).
3 – O usuário deverá ser capaz de visualizar o equivalente do valor binário na base 10, ou seja, terá o resultado em decimal.
E sim, adaptei um pouco o contexto do desafio com o intuito de trazer mais dinamismo para a explicação.
Segundo passo
Desenvolvimento do aplicativo!
Como dito anteriormente irei apresentar como resolvi este desafio utilizando Flutter, então vamos nessa?
Nesta etapa eu segui a premissa de que “alguém com certeza já resolveu esse problema… se sim… como?”, neste caso o próprio repositório do desafio nos apresenta um excelente “apunhado” de exemplos:
Obviamente existe uma excelente dica / alerta neste repositório que é “Tente não ver isso antes de desenvolver sua própria solução“, porém, particularmente, não vejo isso com tão bons olhos, principalmente para quem está iniciando ou até mesmo não conhece nem mesmo o básico da linguagem. Seguindo essa premissa o que fiz foi literalmente abrir todos os exemplos, exceto o do Flutter, e ver como eles se comportavam, visualizei as demonstrações existentes, vi seus códigos e as ideias e entendi todos os exemplos feitos e dai pude entender, com mais precisão, qual a necessidade se deve atender ao resolver com o desafio. Chamo essa etapa em minha vida de viver a necessidade do cliente.
Terceiro passo
Chamo essa etapa carinhosamente de “Papel de Pão”, pois literalmente, se trata de pegar um pedaço de papel e rabiscar a ideia para chegar no esqueleto inicial do desafio.
Quarto passo
Desenvolver Desenvolver Desenvolver Desenvolver Desenvolver!
É claro que possuo uma experiência prévia de desenvolvimento utilizando o Flutter, e devido à internet já possuir bastante conteúdo sobre, serei breve e direto nesta etapa.
Utilizando o esqueleto padrão de um projeto inicial com o Flutter, cheguei no seguinte main.dart:
import 'package:admob_flutter/admob_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:packtudo_bin2dec/hex_materialcolor.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:packtudo_bin2dec/string_locale.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
Admob.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final HexMaterialColor defaultColor = HexMaterialColor("#1559ED");
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''), // English, no country code
const Locale('pt', ''), // my language
],
title: 'PackTudo - Bin2Dec',
theme: ThemeData(
primarySwatch: defaultColor,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'PackTudo - Bin2Dec', defaultColor: defaultColor),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.defaultColor}) : super(key: key);
final String title;
final HexMaterialColor defaultColor;
@override
_MyHomePageState createState() => _MyHomePageState(defaultColor);
}
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState(this.defaultColor);
TextEditingController _controller;
String _label;
final HexMaterialColor defaultColor;
String _resultado = "";
AdmobBannerSize bannerSize;
GlobalKey<ScaffoldState> scaffoldState = GlobalKey();
@override
void initState() {
super.initState();
bannerSize = AdmobBannerSize.FULL_BANNER;
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final StringLocale stringLocale = new StringLocale(context);
if (int.tryParse(_resultado) == null) {
_resultado = stringLocale.result;
}
return Scaffold(
key: scaffoldState,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Center(
child: AdmobBanner(
adUnitId: getBannerAdUnitIdTop(),
adSize: bannerSize,
/*listener: (AdmobAdEvent event, Map<String, dynamic> args) {
handleEvent(event, args, 'Banner');
},*/
))),
Center(
child: Text(
stringLocale.title,
textAlign: TextAlign.center,
style: TextStyle(
color: defaultColor,
fontWeight: FontWeight.bold,
fontSize: 18),
)),
TextField(
keyboardType: TextInputType.numberWithOptions(decimal: true),
maxLength: 63,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp("[0-1]"))
],
onChanged: (value) {
setState(() {
var result = int.tryParse(value, radix: 2);
if (result != 0 && result != null) {
_resultado = result.toString();
} else {
_resultado = stringLocale.result;
}
});
},
controller: _controller,
decoration: InputDecoration(
labelText: _label,
labelStyle: TextStyle(
color: Colors.blue,
),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
),
),
style: TextStyle(color: Colors.blue, fontSize: 25.0),
),
Center(
child: Text(
_resultado,
textAlign: TextAlign.center,
style: TextStyle(
color: defaultColor,
fontWeight: FontWeight.bold,
fontSize: 18),
)),
Expanded(
child: Center(
child: AdmobBanner(
adUnitId: getBannerAdUnitIdDown(),
adSize: bannerSize,
/*listener: (AdmobAdEvent event, Map<String, dynamic> args) {
handleEvent(event, args, 'Banner');
},*/
)))
],
),
));
}
String getBannerAdUnitIdTop() {
return 'ca-app-pub-7123972893709158/4573110184';
}
String getBannerAdUnitIdDown() {
return 'ca-app-pub-7123972893709158/1500224281';
}
void showSnackBar(String content) {
scaffoldState.currentState.showSnackBar(
SnackBar(
content: Text(content),
duration: Duration(milliseconds: 1500),
),
);
}
/*void handleEvent(
AdmobAdEvent event, Map<String, dynamic> args, String adType) {
switch (event) {
case AdmobAdEvent.loaded:
showSnackBar('Bem vindo ao PackTudo - Bin2Dec!');
break;
case AdmobAdEvent.opened:
showSnackBar('Bem vindo ao PackTudo - Bin2Dec!');
break;
case AdmobAdEvent.closed:
showSnackBar('Saindo do PackTudo - Bin2Dec!');
break;
case AdmobAdEvent.failedToLoad:
showSnackBar(
'Falha ao carregar os patrocinadores do PackTudo - Bin2Dec!');
break;
default:
}
}*/
}
As classes auxiliares para utilização de cores do app em HexDecimal.
1 – HexColor:
import 'package:flutter/material.dart';
class HexColor extends Color {
static int _getColorFromHex(String hexColor) {
hexColor = hexColor.toUpperCase().replaceAll("#", "");
if (hexColor.length == 6) {
hexColor = "FF" + hexColor;
}
return int.parse(hexColor, radix: 16);
}
HexColor(final String hexColor) : super(_getColorFromHex(hexColor));
}
2 – HexMaterialColor:
import 'package:flutter/material.dart';
class HexMaterialColor extends MaterialColor {
static int _getColorFromHex(String hexColor) {
hexColor = hexColor.toUpperCase().replaceAll("#", "");
if (hexColor.length == 6) {
hexColor = "FF" + hexColor;
}
return int.parse(hexColor, radix: 16);
}
HexMaterialColor(final String hexColor) : super(_getColorFromHex(hexColor), <int, Color>{
50: Color(0xFFE3F2FD),
100: Color(0xFFBBDEFB),
200: Color(0xFF90CAF9),
300: Color(0xFF64B5F6),
400: Color(0xFF42A5F5),
500: Color(_getColorFromHex(hexColor)),
600: Color(0xFF1E88E5),
700: Color(0xFF1976D2),
800: Color(0xFF1565C0),
900: Color(0xFF0D47A1),
},);
}
Classe para fazer a localização de idiomas no app, StringLocale:
import 'package:flutter/material.dart';
class StringLocale {
Locale _locale;
final BuildContext context;
StringLocale(this.context) {
this._locale = Localizations.localeOf(this.context);
}
Map<String, Map<String, String>> _localizedValues = {
'en': {
'title':
"Enter the binary value in the field below to obtain its decimal equivalent.",
'result': 'Result...'
},
'pt': {
'title': 'Insira o valor binário no campo abaixo para obter seu equivalente decimal.',
'result': 'Resultado...'
},
};
String get title {
return _value('title');
}
String get result {
return _value('result');
}
String _value(String prop){
if(_localizedValues[_locale.languageCode] != null) {
return _localizedValues[_locale.languageCode][prop];
}else{
return _localizedValues['en'][prop];
}
}
}
Por fim chegamos, com as classes acima, no seguinte resultado:
Pontos chaves do código apresentado
Para se obter os números decimais é necessário utilizar o cálculo dos números binários na base 2, no próprio desafio é apresentado diversos materiais de estudo para isto, no Flutter existe um parâmetro excelente na função tryParse, do tipo int, onde podemos informar qual o radix (base) que gostaríamos que um determinado número fosse “parseado“, e ao informamos o valor 2, ele automagicamente efetua todo o cálculo que teríamos de “efetuar na mão”, através de um for… map… e afins…, nos entregando o valor em decimal.
onChanged: (value) {
setState(() {
var result = int.tryParse(value, radix: 2);
if (result != 0 && result != null) {
_resultado = result.toString();
} else {
_resultado = stringLocale.result;
}
});
},
Recomendo fortemente a aula introdutória do canal Ensino e Informação sobre cálculo de números na base 10 (decimais) e na base 2 (binários), este canal é simples e direto e sem “gritaria e papagaiada” como a maioria dos canais existentes hoje em dia…(xD).
Repare que o maxLength aceito é 63 caracteres…64 bits…0 é um bit…1 é outro bit…0101 é um conjunto de 4 bits xD, entendeu o motivo da limitação? (Ps: essa questão aqui foi aborda para instigar sua curiosidade e a pensar em como resolver essa limitação do int).
...TextField(
keyboardType: TextInputType.numberWithOptions(decimal: true),
maxLength: 63,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp("[0-1]"))
]...
Explicações sobre o tipo int utilizado, podem ser vistas na documentação oficial do Flutter.
Caso não tenha ficado claro, recomendo fortemente o vídeo “O Guia +Hardcore de Introdução à Computação do Fabio Akita:
Spolier: O Motivo é basicamente porque a cadeia máxima para representar um número binário é de 64 bits para computador que se utilizam desta arquitetura.
Finalizando...
Para finalizar deixo aqui para fins de consulta o link para o repositório contendo o código fonte do aplicativo apresentado https://github.com/fabiomilson/PackTudo_Bin2Dec.
Dúvidas, questionamentos, felicidades ou mágoas sinta-se a livre e a vontade para comentar ou entrar em contato comigo.