Flutter - Государственное управление
Управление состоянием в приложении - один из наиболее важных и необходимых процессов в жизненном цикле приложения.
Давайте рассмотрим простое приложение корзины покупок.
Пользователь войдет в приложение, используя свои учетные данные.
После того, как пользователь вошел в систему, приложение должно сохранять данные вошедшего в систему пользователя на всем экране.
Опять же, когда пользователь выбирает продукт и сохраняет его в корзину, информация о корзине должна сохраняться между страницами, пока пользователь не выберет корзину.
Информация о пользователе и его корзине в любом случае называется состоянием приложения в этом случае.
Управление состоянием можно разделить на две категории в зависимости от продолжительности определенного состояния в приложении.
Ephemeral- Длится несколько секунд, как текущее состояние анимации, или отдельная страница, например, текущий рейтинг продукта. Flutter поддерживает это через StatefulWidget.
app state- Последнее для всего приложения, такого как данные авторизованного пользователя, информация о корзине и т. Д., Flutter поддерживает его через scoped_model.
Навигация и маршрутизация
В любом приложении переход с одной страницы / экрана на другой определяет рабочий процесс приложения. Способ управления навигацией в приложении называется маршрутизацией. Flutter предоставляет базовый класс маршрутизации - MaterialPageRoute и два метода - Navigator.push и Navigator.pop, чтобы определять рабочий процесс приложения.
МатериалPageRoute
MaterialPageRoute - это виджет, используемый для рендеринга пользовательского интерфейса путем замены всего экрана анимацией для конкретной платформы.
MaterialPageRoute(builder: (context) => Widget())
Здесь конструктор принимает функцию для создания своего содержимого, предоставляя текущий контекст приложения.
Navigation.push
Navigation.push используется для перехода на новый экран с помощью виджета MaterialPageRoute.
Navigator.push( context, MaterialPageRoute(builder: (context) => Widget()), );
Navigation.pop
Navigation.pop используется для перехода к предыдущему экрану.
Navigator.push(context);
Давайте создадим новое приложение, чтобы лучше понять концепцию навигации.
Создайте новое приложение Flutter в студии Android, product_nav_app
Скопируйте папку ресурсов из product_nav_app в product_state_app и добавьте ресурсы в файл 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
Замените код запуска по умолчанию (main.dart) на наш код запуска.
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',)
),
);
}
}
Давайте создадим класс Product для организации информации о продукте.
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
}
Давайте напишем метод getProducts в классе Product для создания наших фиктивных записей продуктов.
static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(
Product(
"Pixel",
"Pixel is the most feature-full phone ever", 800,
"pixel.png"
)
);
items.add(
Product(
"Laptop",
"Laptop is most productive development tool",
2000, "
laptop.png"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png"
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium",
20,
"floppy.png"
)
);
return items;
}
import product.dart in main.dart
import 'Product.dart';
Давайте добавим наш новый виджет RatingBox.
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() =>_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Давайте изменим наш виджет ProductBox для работы с нашим новым классом Product.
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: 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()),
RatingBox(),
],
)
)
)
]
),
)
);
}
}
Давайте перепишем наш виджет MyHomePage, чтобы он работал с моделью продукта и отображал все продукты с помощью ListView.
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 GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
));
}
}
Здесь мы использовали MaterialPageRoute для перехода на страницу сведений о продукте.
Теперь давайте добавим ProductPage, чтобы показать подробную информацию о продукте.
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
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()),
RatingBox(),
],
)
)
)
]
),
),
),
);
}
}
Полный код приложения выглядит следующим образом -
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(
Product(
"Pixel",
"Pixel is the most featureful phone ever",
800,
"pixel.png"
)
);
items.add(
Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.png"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"
)
);
items.add(
Product(
"Pendrive",
"iPhone is the stylist phone ever",
100,
"pendrive.png"
)
);
items.add(
Product(
"Floppy Drive",
"iPhone is the stylist phone ever",
20,
"floppy.png"
)
);
items.add(
Product(
"iPhone",
"iPhone is the stylist phone ever",
1000,
"iphone.png"
)
);
return items;
}
}
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 Navigation 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 GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
)
);
}
}
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
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()),
RatingBox(),
],
)
)
)
]
),
),
),
);
}
}
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1 ? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
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: 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()),
RatingBox(),
],
)
)
)
]
),
)
);
}
}
Запустите приложение и щелкните любой элемент продукта. Будет показана соответствующая страница с подробностями. Мы можем перейти на домашнюю страницу, нажав кнопку «Назад». Страница со списком продуктов и страница с подробностями о продукте в приложении показаны следующим образом: