¿Qué es Flutter?
Es un SDK (Software Development Kit) creado por Google para diseñar interfaces nativas para Android y IPhone, asímo applicaciones de escritorio y aplicaciones web.
Tiene un alto enfoque en el rendimiento. Las aplicaciones desarrolladas con Flutter ofrecen una experiencia realmente nativa de uso.
En el año 2018 salió la primera versión estable de Flutter.
El lenguaje de programación que utiliza es Dart.
Toma muchas ideas de funcionamiento de React.
En la página https://startflutter.com/ podremos consultar multitud de pequeños ejemplos para Flutter.
¿Qué es Dart?
Es un lenguaje de programación creado por Google para programadores de interfaces móviles.
Su página es https://dart.dev/.
En https://dartpad.dev/ puedes probar códigos de Dart online.
Características del lenguaje Dart
En la mayoría de los casos, es posible omitir el tipo de dato:
selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
Podría dejarse así:
selectDate( context) async {
final picked = await showDatePicker(
Es posible no usar la palabra reservada new:
measure = new TextEditingController();
Quedaría así:
measure = TextEditingController();
Fichero de constantes
En Dart puedes declarar constantes estáticas fuera de una clase. En estos casos, no será necesario el modificador static.
const String APP_NAME = 'El diario de Papa';
import 'utils/consts.dart';
...
appBar: AppBar(
title: Text(APP_NAME),
),
El editor de código
Tenemos dos opciones: Visual Studio Code o Android Studio.
El Android Studio tiene mucho mayores requisitos de potencia. No obstante, si la máquina lo permite, es recomendable utilizar el Android Studio por encima del Visual Studio.
Instalación de Flutter
- Descargamos el SDK de Flutter https://flutter.dev/docs/get-started/install.
- Tanto si vamos a usar Visual Studio Code o Android Studio para desarrollar, instalamos las extensiones de Flutter y Dart.
- Para poder ejecutar los comandos de Flutter, sería muy útil añadir esta la carpeta bin de flutter a las variables de entorno de tu sistema operativo.
- ctrl + shift + p → Flutter: New Application Project.
El editor
Estructura de un proyecto en Flutter
En la raíz de un proyecto Flutter tenemos las siguientes carpetas:
- Android → Ficheros específicos para la compilación de un proyecto en Android.
- ios → Ficheros específicos para la compilación de un proyecto in ios.
- web → Ficheros específicos para la compilación de un proyecto web.
- lib → Aquí estaremos trabajando el 99.9% del tiempo. Aquí está el código fuente de nuestra aplicación, compartido para todas las plataformas.
- pubspec.yaml → Fichero de configuración en el que deben estar referenciados todos los assets que cargue la aplicación (video, audio, imágenes, fuentes…)
Compilación
Para Android
Compilación en modo debug. Generará un apk en build/app/outputs/apk
flutter build apk
flutter build apk --debug
Para web
flutter build web
Si vamos a instalar la aplicación en una subcarpeta tendremos que sobreescribir la ruta en la siguiente etiqueta del fichero /web/index.html:
<base href="$FLUTTER_BASE_HREF">
Hola Mundo con Flutter
Carga los Widgets del SDK de Flutter.
import 'package:flutter/material.dart';
Definimos el punto de arranque de nuestra aplicación. Arrancamos el Widget MyApp.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp();
// El método build de la clase StatelessWidget nos devuelve el Widget que será visualizado
@override
Widget build(BuildContext context) {
return MaterialApp(
// Este título identifica a la aplicación pero lo vemos, por ejemplo, cuando activamos el Task Manager para navegar entre nuestras aplicaciones
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green, // Color principal de la plantilla usada
),
home: Scaffold(
appBar: AppBar(
title: Text("Hello World Title"),
),
body: Center(child: Text("Hello World")),
),
);
}
}
Widgets
Un Widget en Flutter, es el equivalente a un View en Android y en React Native o a un div en HTML.
En Android desarrollamos interfaces a partir de un documento XML. En Flutter lo hacemos a través del Widget Tree.
Cuando en Android Studio pulsamos sobre un Widget, nos saldrá una bombilla amarilla opciones para envolverlo en otro Widget, o borrar el Widget que nos serán muy cómodas para desarrollar. También podemos acceder a estas opciones pulsando alt + Enter.
El árbol de widgets
Es el Widget que devuelve el método build.
Widget build(BuildContext context) {
Es recomendable que todos los elementos del árbol de Widgets vayan separados entre sí por coma, con esto lograremos que al formatear nuestro código se separen de forma más clara.
Scaffold
El Scaffold de un Widget es el esqueleto ( o literalmente traducido andamio) en el que se sostienen todos sus elementos visuales.
El Scaffold de un Widget es el esqueleto ( o literalmente traducido andamio) en el que se sostienen todos sus elementos visuales.
Scaffold(
appBar:,
body:,
bottomNavigationBar:,
floatingActionButton:,
floatingActionButtonLocation:,
);
return Scaffold(
appBar: AppBar(
title: const Text('Floating Action Button'),
),
body: const Center(
child: Text('Press the button below!')
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Add your onPressed code here!
},
child: const Icon(Icons.navigation),
backgroundColor: Colors.green,
),
);
Text
Documentación con ejemplos: https://api.flutter.dev/flutter/widgets/Text-class.html
Text(
'Hello, $_name! How are you?',
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold
backgroundColor: Colors.blue
),
)
Si quisieramos poner un padding a un Text, necesitaríamos un container:
Container(
color: Colors.yellow,
padding: const EdgeInsets.all(12),
child: Text('Peso',
style: TextStyle( fontSize: 40),
),
),
Si quisieramos alinear a la izquierda un texto que está dentro de una columna, es probable que tengamos que asignar a la columna la propiedad:
crossAxisAlignment: CrossAxisAlignment.start,
Textfield
class MyClass extends State<AddItem> {
TextEditingController? textEditingControllerItem;
void initState(){
super.initState();
textEditingControllerItem = TextEditingController();
}
...
TextField(
controller: textEditingControllerWeight,
keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed(
hintText: 'Introduce el peso del niño'
),
),
Column y Row
Column: por defecto, su ancho es el ancho del contenido y su alto, el alto la pantalla.
Row: por defecto su ancho es el ancho de la pantalla y su alto, el alto del contenido.
Esto puede ocasionar que su contenido no se centre cuando estamos dentro de un Widget Center. Para evitarlo, podemos usar la propiedad:
mainAxisSize: MainAxisSize.min,
Stack y Container
Stack nos permite hacer superposición de elementos.
Container es cajita en la que podemos meter información. Equivaldría a un div, al que puedes meter un padding, un margin, un border…
Stack(
children: <Widget>[
Container(
width: 100,
height: 100,
color: Colors.red,
),
Container(
width: 90,
height: 90,
color: Colors.green,
),
Container(
width: 80,
height: 80,
color: Colors.blue,
),
],
)
- En el caso de que un Container no tenga unas dimensiones (width y height) asignadas, adaptará su tamaño a su contenido.
- El container tiene un único hijo.
- Un Container que tiene propiedad color no puede tener Widget Decoration, y viceversa.
Aceptan una colección de hijos. Su contenido se alinea vertical y horizontalmente respectivamente.
En ambos casos, el tamaño de estos widgets es el 100% del tamaño del Widget que los envuelve. En estos casos de tamaño variable, si queremos ponerles un child de un elemento cuyo tamaño se ajuste al padre, como el padre tiene un tamaño variable, no se verán. Para solucionar el problema, usaremos el Widget Expanded, que toma el tamaño del Widget padre.
Este código funciona, los TextField se visualizan
child: Row(
children: [
Expanded(child: TextField()),
Expanded(child: TextField()),
],
),
Este código no funciona, los TextField no se visualizan:
child: Row(
children: [
TextField(),
TextField(),
],
),
Atributos importantes:
Alinea todo el contenido según el valor recibido.
crossAxisAlignment: CrossAxisAlignment.start,
Poner un borde redondeado:
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(12)),
Debes tener en cuenta que si estamos utilizando decoration debemos ponerle un color al panel.
Center
Este widget sólo acepta un único hijo (child). Centra su contenido horizontal y vericalmente.
Align
Este widget permite alinear su contenido a derecha, izquierda, arriba o abajo.
Expanded
Es un widget que hace que su contenido se estire todo lo posible.
Sizedbox
Permite poner separaciones entre widgets. Es similar a lo que haríamos si usásemos paddings o margins.
SizedBox(
width: 16,
),
Padding
Nos permite añadir padding a los elementos.
Padding(
padding: const EdgeInsets.all(12.0),
child: ...
),
El parámetro padding acepta varios valores:
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
padding: const EdgeInsets.only(right: 30),
SingleChildScrollView
Para que nuestro contenido tenga Scroll. Puede envolver cualquier Widget.
A menudo la columna de nuestro contenido deberá estar dentro de un SingleChildScrollView, ya que al pulsar sobre un cuadro de texto la pantalla se comprimirá para mostrar el teclado y no nos quedará espacio.
GradientBack
Es una especie de div con degradado.
CardImageList
Es un carrusel de elementos.
ListView
Muestra un listado de elementos
Checkbox
Radio
El value de un radio puede ser de cualquier tipo, no sólo de tipo String.
Para que la opción marcada en un radio button se visualice, habrá que repintar la pantalla llamando al método setState.
La propiedad groupValue tiene el valor escogido de los 3. Cuando groupValue sea igual a value, el radio se marcará.
Radio(
autofocus: true,
value: 'F',
groupValue: sexType,
onChanged: (String? value) => onSexChange(value)),
...
onSexChange(String? nuevoValor) {
setState(() {
sexType = nuevoValor;
});
}
Slider
Form
DatePicker
DateTime selectedDate = DateTime.now();
...
_selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate, // Refer step 1
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
if (picked != null && picked != selectedDate)
setState(() {
selectedDate = picked;
});
}
Botones
OutlinedButton(onPressed: onPressed, child:Text('hola')),
TextButton(onPressed: onPressed, child: Text('hola')),
ElevatedButton(onPressed: onPressed, child: Text('Hola'))
IconButton(
icon: const Icon(Icons.check),
color: Colors.white,
onPressed: () {},
iconSize: 30,
)
Cargar botón con imagen
TextButton(
onPressed: () => updateCheckPoint(index),
child: Image.asset(
'assets/checked.png',
width: 30,
fit: BoxFit.cover,
),
)
GestureDetector e InkWell
Nos permiten detectar eventos de todo tipo sobre su contenido (tap, doble tap, drag).
GestureDetector(
onTap: () => setState(() => sexType = 'M'),
child: Text('Masculino')),
El InkWell tiene animaciones cuando interactuamos con él, pero soporta un menor tipo de eventos.
InkWell(
onTap: () => setState(() => sexType = 'M'),
child: MonteserinText('Masculino')),
Renderizado condicional
widgets...
if (condicion)
Boton(...),
widgets...
El if debe hacerse sin llaves.
widgets...
condicion ? boton1() : boton2(),
widgets...
widgets...
Visibility(visible: condicion, child: boton(...)),
widgets...
Asincronía en Flutter
Cuando un método devuelve Future<loQueSea>, ese método es asíncrono.
No sabemos cuando un método asíncrono va a concluir.
Los métodos asíncronos suelen tener el modificador async:
Future<String> loadString(String cosas) async {
Para procesar un Future, tenemos dos opciones.
- Usar await, que detendrá la ejecución del código hasta que haya concluído el método.
final String data = await rootBundle.loadString('assets/json/data.json');
El modificador await convierte a la función que lo envuelve en asíncrona, y por tanto tendremos que añadirle el modificador async. Tiene sentido porque cuando detenemos la ejecución de una función hasta que no haya concluído cierto método, la estamos convirtiendo en asíncrona.
- Usar then:
final resultado = rootBundle.loadString('assets/json/data.json');
resultado.then((value) {
}).catchError((error) {
}).whenComplete(() {
});
Cargar una tipografía
pubspec.yml
flutter:
fonts:
- family: kravitz
fonts:
- asset: assets/fonts/kravitz.ttf
- family: routhem
fonts:
- asset: assets/fonts/routhem.ttf
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.yellow,
fontFamily: 'kravitz'
),
Text('Hola',
style: TextStyle(font-fontFamily: 'routhem'),
),
Leer un JSON con Flutter
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/json/
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
List items = [];
Future<void> readJson() async {
final String response = await rootBundle.loadString('assets/data.json');
items = await jsonDecode(response);
setState(() {
});
}
Pintar los datos en pantalla
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return Card(
color: Colors.green[100],
child: ListTile(
title: Text(item["name"]),//item.,
subtitle: Text(item["days"].toString()),//item.,
),
);
},
),
),
Importando un Widget definido en un fichero externo:
import 'package:flutter/material.dart';
import 'widget_1.dart';
class MyApp extends StatelessWidget {
const MyApp();
@override
Widget build(BuildContext context) {
return MaterialApp(
...
home: Scaffold(
...
body: new Widget1(),
)
);
}
}
widget_1.dart
import 'package:flutter/material.dart';
class Widget1 extends StatelessWidget {
const Widget1();
@override
Widget build(BuildContext context) {
final myRow = Row(
children: [
Container(
width: 50,
height: 50,
color:Colors.pink
),
Container(
width: 50,
height: 50,
color:Colors.blue
)
],
);
return myRow;
}
}
Mostrando el contenido de un array con un ListView
body: ListView.builder(
itemCount: names.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
margin: EdgeInsets.all(2),
color: Colors.green,
child: Center(
child: Text(
${names[index]}',
style: TextStyle(fontSize: 22, color: Colors.white),
)
),
);
}
)
Llamadas a funciones desde un Widget
El siguiente código es correcto:
OutlinedButton(onPressed: calculate, child: Text('Calcular'))
...
calculate()
Una función sin paréntesis es un puntero a dicha función y podrá ser asignada a un parámetro de tipo función.
Pero este código no es correcto:
OutlinedButton(onPressed: calculate(), child: Text('Calcular'))
En el código anterior, la función calculate se está llamando en el momento de crear el Widget, en lugar de cuando presionemos sobre él. Lo correcto sería haber hecho:
OutlinedButton(onPressed: () => calculate(), child: Text('Calcular'))
Llamar a una función del padre desde un widget del hijo
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MonteserinDropDownButton extends StatelessWidget {
final Function(String?) onChanged;
MonteserinDropDownButton(this.items, this.val, this.onChanged,);
@override
Widget build(BuildContext context) {
return DropdownButton(
...
onChanged: (valor)=> onChanged(valor),
);
}
}
initState()
Este método se ejecuta una sola vez cuando se crea el componente y suele utilizarse para inicializar variables miembro que forman parte del estado de nuestro stateFlulWidget.
Sólo está disponible en los stateFulWidget.
Este método se ejecuta antes de renderizar el componente. Mucho cuidado con llamar a métodos asíncronos desde él, ya que entonces el componente se renderizará antes de que el estado se inicialice y dará error.
late List items;
void initState() {
getItems();
}
getItems() async{
items = jsonDecode(itemsRecovered);
}
Hacer un botón con estilo personalizado
Formularios
- Para recoger y procesar datos introducidos por el usuario, los elementos del formulario deberían estar dentro de un StateFulWidget (En nuestro caso, HomePage).
- Para recoger valores de los Textfield, utilizaremos la propiedad controller.
- Las variables n1 y n2 que almacenarán los valores introducidos por el usuario serán declarados como variables globales.
- El constructor de un StateFulWidget se ejecuta cada vez que este se repinta. En nuestro caso, no será necesario inicializar las variables n1 y n2 cada vez que la aplicación se repinta, y por eso para inicializarlas hemos usado el método initState.
- Después de calcular el resultado, en el método result, debemos repintar para que este se visualice en pantalla. Para ello llamámos al método setState;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late TextEditingController n1, n2;
String result = '';
@override
void initState() {
super.initState();
n1 = new TextEditingController();
n2 = new TextEditingController();
}
onPressed() {
print('n1: ${n1.text} n2: ${n2.text}' );
final r = int.parse(n1.text) + int.parse(n2.text);
result = r.toString();
setState(() {
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: [
Expanded(child: TextField(controller:n1, keyboardType: TextInputType.number)),
SizedBox(
width: 16,
),
Expanded(child: TextField(controller: n2,keyboardType: TextInputType.number,)),
Expanded(child: Text(result)),
OutlinedButton(onPressed: onPressed, child: Text('Enviar'))
],
),
),
);
}
}
Descargar Recurso
Añadir un DatePicker
import 'package:flutter/src/material/date_picker.dart';
...
selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate, // Refer step 1
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
}
}
...
OutlinedButton(onPressed: () => selectDate(context), child: Text('Fecha de nacimiento')),
Creación de nuestros propios Widgets
Widgets sin estado (StateLessWidget)
El snippet para codificarlos es: stless
El usuario no va a interactuar directamente con ellos.
Widgets con estado (StatefulWidget)
Son aquellos con los que el usuario puede interactuar directamente.
El snippet para codificarlos es: stful
Gestión de nulos
Como recomendación, trataremos de inicializar las variables miembro en su declaración, en lugar de usar el modificador late o el operador ?.
Modificador late
Nos permite declarar una variable a la que luego debemos asignarle un valor concreto. Si intentamos acceder a una variable late a la que no le hemos asignado un valor, tendremos un error.:
late TextEditingController textEditingControllerSex;
late se suele utilizar para variables inicializadas en el initState. Este es el ejemplo ideal:
@override
void initState() {
textEditingControllerSex = TextEditingController();
}
Una variable late no es que valga null, es que no está inicializada.
Operador ?.
Este operador significa: si el objeto es nulo, no hagas esto.
textEditingControllerSex?.dispose();
En el ejemplo anterior, en el caso de que textEditingControllerSex sea nulo, no se ejecutrará el método dispose().
Operador ?
Para declarar una variable que pueda tener valores nulos, debemos utilizar la ? después del tipo.
String? sexType;
Existen funciones que devuelven objetos que pueden ser null. En estos casos, debemos utilizar el operador ? a continuación del tipo de la variable que almacena el valor. Este operador, indica que esa variable almacena un valor que puede ser nulo..
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate, // Refer step 1
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
Operador ??
En ocasiones, al asignar un valor a una variable, debemos asegurarnos de que el valor asignado no es nulo. Podemos hacer esto utilizando el operador ?? que asignará el valor indicado en el caso de que el valor que se iba a asignar primeramente sea nulo.
sexType = nuevoValor ?? '';
Navegar a otra pantalla
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
Cerrar una pantalla abierta
Navigator.pop(context);
Cerrar una pantalla abierta pasando información a la pantalla a la que regreso
Navigator.pop(context, true);
Recoger parámetros de una pantalla que cerramos
Es fundamental utilizar e modificador await, que esperará a que cerremos la pantalla.
openScreen() async {
final otherScreenPassedValue = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddItem()),
);
if ( otherScreenPassedValue != null && otherScreenPassedValue as bool) {
getItems();
}
}
Pasando parámetros
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
valorAPasar: valorAPasar)
),
);
class SecondPage extends StatefulWidget {
final valorAPasar;
...
@override
_EditItemState createState() => _EditItemState();
}
class _SecondPage extends State<EditItem> {
void initState(){
print(widget.valorAPasar)
}
Añadir publicidad
1. Registramos un cuenta de Google Ads en el caso de no tenerla y accedemos a https://apps.admob.com.
2. Desde la página de admob, creamos una nueva App: App → Add App. Seguimos los pasos que se nos indican.
3. Llegaremos a una página en la que se nos propondrá crear un nuevo Unit (una nueva publicidad). Escogemos el banner.
4. En el caso de una publicidad para Android, debemos copiar el App Id de nuestra aplicación (disponible en Apps → App Settings) en un nuevo nodo meta-data (en la propiedad android:value) del fichero manifest.
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.percentiles.percentiles">
<application
android:label="percentiles"
android:icon="@mipmap/ic_launcher">
<meta-datap
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
Un error muy típico consiste en no meter el meta-data dentro del nodo application. Para evitarlo, meteremos el meta-data inmediatamente dentro y al principio del nodo application.
5. Cargamos el módulo de publicidad:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
google_mobile_ads: ^0.13.4
Después de modificar los ficheros gradle o el manifest de Android hay que hacer un Tools → Flutter → Clean. Y luego un pub get.
6. Inicializamos los anuncios en el main de nuestra aplicación:
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
import 'homepage.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
MobileAds.instance.initialize();
runApp(const MyApp());
}
6. En la página donde querramos mostrar el banner:
import 'package:google_mobile_ads/google_mobile_ads.dart';
late BannerAd _bannerAd;
...
_bannerAd = BannerAd(
// adUnitId: 'ca-app-pub-3340658405798791/3405324824' ,
adUnitId: 'ca-app-pub-3940256099942544/6300978111', // PARA PRUEBAS //
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener()
);
_bannerAd.load();
// en el widget tree. Los widgets de anuncio necesita que le indiquemos la altura que van a ocupar.:
Container(height: 100,child: AdWidget(ad: _bannerAd)),
Para probar los banners usaremos un adUnitId de pruebas. Luego, si todo va bien, lo sustituiremos por el que hemos obtenido en la web de admob.
https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter#0
Crear un Widget reutilizable a partir de otros Widgets:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MonteserinTitle extends StatelessWidget {
final String _texto;
MonteserinTitle(this._texto);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(12)),
child: Text(_texto,
style: TextStyle( fontSize: 40),
),
);
}
}
Llamar a una función al cabo de un tiempo, incluso en segundo plano
Lo métodos de Timer se ejecutarán incluso en segundo plano.
class _MyHomePageState extends State<MyHomePage> {
int _secondsSelect = 10;
static const int INCR = 5;
bool _cronoActive = false;
int _secondsRemain = 0;
Timer? timer;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!_cronoActive)
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
if (_secondsSelect - INCR > 0)
setState(() {
_secondsSelect -= INCR;
});
},
icon: Icon(Icons.chevron_left)),
Text("$_secondsSelect Segundos"),
IconButton(
onPressed: () {
setState(() {
_secondsSelect += INCR;
});
},
icon: Icon(Icons.chevron_right))
],
),
ElevatedButton(
onPressed: _cronoActive ? _stopCrono : _startCrono, child: Text(_cronoActive ? "Detener" : "Activar")),
if (_cronoActive) Text(_secondsRemain.toString())
],
),
),
);
}
_startCrono() {
setState(() {
_cronoActive = true;
_secondsRemain = _secondsSelect;
timer = Timer.periodic(const Duration(seconds: 1), (Timer t) => _updateProgress());
});
}
_updateProgress() {
setState(() {
_secondsRemain--;
if (_secondsRemain <= 0) {
FlutterBeep.beep();
_stopCrono();
_startCrono();
}
});
}
_stopCrono() {
setState(() {
timer!.cancel();
_cronoActive = false;
});
}
}
Flame
Es un motor de videojuegos 2D para Flutter.
dependencies:
flutter:
sdk: flutter
flame: 0.29.4
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyGame().widget);
class MyGame extends BaseGame {
@override
void render(Canvas canvas) {
}
@override
void update(double t) {
}
}
Internacionalización
1. Cargamos las dependencias de internacionalización
dependencies:
flutter:
sdk: flutter
flutter_localizations: # Add this line
sdk: flutter # Add this line
intl: ^0.17.0 # Add this line
# The following section is specific to Flutter.
flutter:
generate: true # Add this line
2. Creamos un nuevo fichero:
l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
3. ${FLUTTER_PROJECT}/lib/l10n/app_en.arb
{
"helloWorld": "Hello World!",
"@helloWorld": {
"description": "The conventional newborn programmer greeting"
}
}
4. Definimos las dependencias para nuestra Material App:
import 'package:flutter_localizations/flutter_localizations.dart';
return const MaterialApp(
title: 'Localizations Sample App',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: MyHomePage(),
);
5. Para usar la localización:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Text(
AppLocalizations.of(context)!.empty),
),
Cambiar el icono de la aplicación
1. Abriremos la aplicación con Android Studio ( Menú Tools → Flutter → Open Android Module in Android Studio).
2. Pulsamos con el botón derecho sobre alguno de las carpetas de la aplicación → New → Image Asset.
3. Cargamos la imagen de la aplicación. Aunque inicialmente hay cargado un fichero xml, no habrá problema en cargar una imagen.
Publicación
- Generación del fichero keystore. Menú Tools → Flutter → Open Android module in Android Studio → Menú Build de la nueva interfaz de Android Studio que se abre → Generate Signed Bundle / APK
- Modicamos los ficheros necesarios dentro de nuestro proyecto de Flutter para hacer el build. Tienes la documentación oficial de como hacerlo en este enlace. Puedes seguir la guía a partir del paso Reference the keystore from the app.
- Cuando esté todo configurado pulsamos el menú Build → Flutter → Build App Bundle.