Flutter - Estado de la aplicación
Estado de la aplicación - scoped_model
Flutter proporciona una manera fácil de administrar el estado de la aplicación usando el paquete scoped_model. El paquete Flutter es simplemente una biblioteca de funciones reutilizables. Aprenderemos sobre los paquetes de Flutter en detalle en los próximos capítulos.
scoped_model proporciona tres clases principales para permitir una gestión de estado robusta en una aplicación que se discute en detalle aquí:
Modelo
El modelo encapsula el estado de una aplicación. Podemos usar tantos Model (heredando la clase Model) como sea necesario para mantener el estado de la aplicación. Tiene un método único, notifyListeners, que debe llamarse cada vez que cambia el estado del modelo. notifyListeners hará las cosas necesarias para actualizar la interfaz de usuario.
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
Product(this.name, this.description, this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating; notifyListeners();
}
}
Modelo de alcance
ScopedModel es un widget, que contiene el modelo dado y luego lo pasa a todos los widgets descendientes si se solicita. Si se necesita más de un modelo, entonces necesitamos anidar el ScopedModel.
Modelo único
ScopedModel<Product>(
model: item, child: AnyWidget()
)
Modelo múltiple
ScopedModel<Product>(
model: item1,
child: ScopedModel<Product>(
model: item2, child: AnyWidget(),
),
)
ScopedModel.of es un método que se utiliza para obtener el modelo subyacente al ScopedModel. Se puede utilizar cuando no es necesario realizar cambios en la interfaz de usuario aunque el modelo vaya a cambiar. Lo siguiente no cambiará la IU (calificación) del producto.
ScopedModel.of<Product>(context).updateRating(2);
Modelo Descendiente
ScopedModelDescendant es un widget, que obtiene el modelo del widget de nivel superior, ScopedModel, y crea su interfaz de usuario cada vez que cambia el modelo.
ScopedModelDescendant tiene dos propiedades: constructor y secundario. child es la parte de la interfaz de usuario que no cambia y se pasará al constructor. El constructor acepta una función con tres argumentos:
content - ScopedModelDescendant pasa el contexto de la aplicación.
child - Una parte de la interfaz de usuario, que no cambia según el modelo.
model - El modelo real en esa instancia.
return ScopedModelDescendant<ProductModel>(
builder: (context, child, cart) => { ... Actual UI ... },
child: PartOfTheUI(),
);
Cambiemos nuestra muestra anterior para usar scoped_model en lugar de StatefulWidget
Cree una nueva aplicación Flutter en Android Studio, product_scoped_model_app
Reemplace el código de inicio predeterminado (main.dart) con nuestro código product_state_app
Copie la carpeta de activos de product_nav_app a product_rest_app y agregue activos dentro del archivo pubspec.yaml
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
Configure el paquete scoped_model en el archivo pubspec.yaml como se muestra a continuación: -
dependencies: scoped_model: ^1.0.1
Aquí, debe usar la última versión del paquete http
Android Studio alertará de que pubspec.yaml está actualizado.
Haga clic en la opción Obtener dependencias. Android Studio obtendrá el paquete de Internet y lo configurará correctamente para la aplicación.
Reemplace el código de inicio predeterminado (main.dart) con nuestro código de inicio.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Text( 'Hello World', )
),
);
}
}
Importe el paquete scoped_model en el archivo main.dart.
import 'package:scoped_model/scoped_model.dart';
Creemos una clase de producto, Product.dart para organizar la información del producto.
import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
Product(this.name, this.description, this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating;
notifyListeners();
}
}
En este caso, hemos utilizado notifyListeners para cambiar la interfaz de usuario cada vez que se cambia la calificación.
Escribamos un método getProducts en la clase Product para generar nuestros registros de productos ficticios.
static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(
Product(
"Pixel",
"Pixel is the most feature-full phone ever", 800,
"pixel.png", 0
)
);
items.add(
Product(
"Laptop", "Laptop is most productive development tool", 2000,
"laptop.png", 0
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting", 1500,
"tablet.png", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100, "pendrive.png", 0
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium", 20,
"floppy.png", 0
)
);
return items;
}
import product.dart in main.dart
import 'Product.dart';
Cambiemos nuestro nuevo widget, RatingBox para admitir el concepto scoped_model.
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 1
? Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
), color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
) : Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
Aquí, hemos ampliado RatingBox de StatelessWidget en lugar de StatefulWidget. Además, hemos utilizado el método updateRating del modelo de producto para establecer la calificación.
Modifiquemos nuestro widget ProductBox para que funcione con las clases Product, ScopedModel y ScopedModelDescendant.
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item)
{ return RatingBox(item: item); }
)
],
)
)
)
)
]
),
)
);
}
}
Aquí, hemos incluido el widget RatingBox dentro de ScopedModel y ScopedModelDecendant.
Cambie el widget MyHomePage para usar nuestro widget ProductBox.
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
)
);
}
}
Aquí, hemos utilizado ListView.builder para construir dinámicamente nuestra lista de productos.
El código completo de la aplicación es el siguiente:
Product.dart
import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
Product(this.name, this.description, this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);n
} void cn "Laptop is most productive development tool", 2000, "laptop.png", 0));
items.add(
Product(
"Tablet"cnvn,
"Tablet is the most useful device ever for meeting", 1500,
"tablet.png", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium", 100,
"pendrive.png", 0
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium", 20,
"floppy.png", 0
)
)
; return items;
}
main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'Product.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
)
);
}
}
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 1? Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3 ?
Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item, child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
this.item.name, style: TextStyle(
fontWeight: FontWeight.bold
)
),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
}
)
],
)
)
)
)
]
),
)
);
}
}
Finalmente, compile y ejecute la aplicación para ver su resultado. Funcionará de manera similar al ejemplo anterior, excepto que la aplicación utiliza el concepto scoped_model.