О сайте
Привет, добро пожаловать на 2вторую части серии статей о разработке приложений во Flutter. В прошлой части мы успешно создали стартовое приложение flutter и настроили иконку запуска и заставку для нашего приложения «Astha — Being Hindu». На данный момент у нашего приложения есть пользовательская иконка и заставка, появляющаяся при запуске приложения. В этом разделе руководства мы сделаем экран входа в приложение и применим его к нашему приложению. Для этого мы установим пакеты GoRouter, Provider и Shared_Preferences для нашего приложения.
Включать или нет
Давайте снова посмотрим на поток пользовательских экранов с изображения ниже.
Давайте рассмотрим логику маршрутизации, которую мы будем использовать для включения пользователя.
Был ли пользователь/приложение зарегистрировано?
-> Если да, то нет необходимости в регистрации до конца жизни приложения, т.е. пока приложение не будет удалено.
-> Если нет, то перейдите на экран регистрации только один раз и больше никогда в течение жизни приложения.
Итак, как мы собираемся этого добиться?
Все просто, в go router есть опция redirect, где свойство state свойства redirect можно использовать для написания проверочных утверждений, чтобы узнать о текущих маршрутах: где он сейчас, куда он направляется, и тому подобное. С помощью этого мы можем перенаправить на борт или нет.
Это замечательно, но что/как мы будем проверять?
Вот тут-то и приходят на помощь общие предпочтения. Мы можем хранить простые данные в локальном хранилище с помощью общих предпочтений. Итак, во время инициализации приложения:
-
Мы получим целое число/ключ, хранящийся в локальном хранилище, который отвечает за подсчет состояния бортовой сети, с помощью общих предпочтений.
-
Когда приложение будет запущено в первый раз, общие предпочтения вернут null, потому что целое число еще не существует. Для нас это эквивалентно тому, что мы никогда не были на борту.
-
После этого на маршрутизаторе мы проверим, является ли целое число null, если да, то перейдем к экрану onboarding.
-
По завершении включения мы, наконец, установим несуществующее целое число в ненулевое значение и сохраним его в локальном хранилище с помощью пакета Provider.
-
Теперь, когда мы запустим приложение во второй раз, маршрутизатор обнаружит, что целое число больше не является нулевым значением, поэтому он перенаправит нас на следующую страницу, а не на экран onboard.
Установка пакетов
Код проекта до настоящего момента можно найти в этом репозитории. Итак, давайте перейдем в файл pubspec.yaml нашего проекта и добавим следующие пакеты.
dependencies:
flutter:
sdk: flutter
flutter_native_splash: ^2.1.1
# Our new pacakges
go_router: ^3.0.5
shared_preferences: ^2.0.13
provider: ^6.0.2
Переформатировать
Прежде чем мы начнем действовать, давайте сделаем некоторые изменения в нашем проекте. Сначала мы создадим несколько файлов и папок.
#Your cursor should be inside lib your lib folder
# make some folder
mkdir globals screens globals/providers globals/settings globals/settings/router globals/settings/router/utils screens/onboard screens/home
# make some files
touch app.dart globals/providers/app_state_provider.dart globals/settings/router/app_router.dart globals/settings/router/utils/router_utils.dart screens/onboard/onboard_screen.dart screens/home/home.dart
Теперь файл main.dart выглядит следующим образом.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
// concrete binding for applications based on the Widgets framewor
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.black38),
);
runApp(const Home());
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(child: Text("Home Page")),
),
);
}
}
Затем разделите файл main.dart и переместите часть его содержимого в файл home.dart. Давайте удалим класс Home из файла main в home.dart. Теперь наши файлы выглядят следующим образом:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:temple/screens/home/home.dart';
void main() {
// concrete binding for applications based on the Widgets framewor
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.black38),
);
runApp(const Home());
}
home.dart
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(child: Text("Home Page")),
),
);
}
}
Утилиты маршрутизатора
В процессе маршрутизации нам потребуется предоставить несколько свойств, таких как путь маршрутизатора, именованный маршрут, заголовок страницы и т.д. Будет эффективно, если эти значения могут быть переданы на аутсорсинг из модуля. Поэтому мы создали файл utils/router_utils.dart.
router_utils.dart
// Create enum to represent different routes
enum APP_PAGE {
onboard,
auth,
home,
}
extension AppPageExtension on APP_PAGE {
// create path for routes
String get routePath {
switch (this) {
case APP_PAGE.home:
return "/";
case APP_PAGE.onboard:
return "/onboard";
case APP_PAGE.auth:
return "/auth";
default:
return "/";
}
}
// for named routes
String get routeName {
switch (this) {
case APP_PAGE.home:
return "HOME";
case APP_PAGE.onboard:
return "ONBOARD";
case APP_PAGE.auth:
return "AUTH";
default:
return "HOME";
}
}
// for page titles to use on appbar
String get routePageTitle {
switch (this) {
case APP_PAGE.home:
return "Astha";
default:
return "Astha";
}
}
}
Перейти к маршрутизатору
Наконец, мы можем перейти к файлу маршрутизатора, где мы будем создавать маршруты и логику перенаправления. Итак, в файле app_router.dart.
Создайте класс AppRouter.
import 'package:go_router/go_router.dart';
import 'package:temple/screens/home/home.dart';
import 'utils/router_utils.dart';
class AppRouter {
get router => _router;
final _router = GoRouter(
initialLocation: "/",
routes: [
GoRoute(
path: APP_PAGE.home.routePath,
name: APP_PAGE.home.routeName,
builder: (context, state) => const Home(),
),
],
redirect: (state) {});
}
AppRouter — это класс маршрутизатора, который мы будем использовать в качестве провайдера. Маршрутизатор, который мы только что создали, теперь имеет один маршрутизатор «/», который является маршрутом к нашей домашней странице. Аналогично, свойство initialLocation указывает маршрутизатору перейти на домашнюю страницу сразу после запуска приложения. Но если будут выполнены некоторые условия, то он может быть перенаправлен куда-то еще, что делается через redirect. Однако нам еще предстоит реализовать наш маршрутизатор. Для этого перейдем к файлу app.dart.
Используйте Router вместо Navigator.
MaterialApp.Router создает MaterialApp, который использует Router вместо Navigator. Ознакомьтесь с различиями здесь. Нам нужно использовать декларативный способ для нашего go_router.
app.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:temple/globals/settings/router/app_router.dart';
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (context) => AppRouter()),
],
child: Builder(
builder: ((context) {
final GoRouter router = Provider.of<AppRouter>(context).router;
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate);
}),
),
);
}
}
Класс MyApp будет родительским классом для нашего приложения, т.е. классом, используемым в runApp(). Следовательно, именно здесь мы будем использовать маршрутизатор. Более того, мы возвращаем MultiProvider, потому что по мере роста приложения мы будем использовать множество других провайдеров.
Как уже упоминалось, нам нужно передать класс MyApp в метод runApp() в нашем файле main.dart.
// Insde main() method
void main() {
............
// Only change this line
runApp(const MyApp());
//
}
Теперь сохраните все файлы и запустите приложение в эмуляторе. Вы увидите домашнюю страницу, которая будет выглядеть следующим образом.
Провайдер
Мы будем писать нашу логику о состоянии борта в классе провайдера, и поскольку это глобальное состояние, мы напишем его в файле app_state_provider.dart в папке «lib/globals/providers».
app_state_provider.dart
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppStateProvider with ChangeNotifier {
// lets define a method to check and manipulate onboard status
void hasOnboarded() async {
// Get the SharedPreferences instance
SharedPreferences prefs = await SharedPreferences.getInstance();
// set the onBoardCount to 1
await prefs.setInt('onBoardCount', 1);
// Notify listener provides converted value to all it listeneres
notifyListeners();
}
}
Внутри функции hasOnboarded() мы устанавливаем целое число onBoardCount в единицу или не-нулевое значение, как упоминалось ранее.
Теперь, знаете ли вы, как реализовать этот провайдер в нашем приложении? Да, нам нужно добавить еще один провайдер к MultiProvider в app.dart.
app.dart
import 'package:temple/globals/providers/app_state_provider.dart';
....
.....
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppStateProvider()),
Provider(create: (context) => AppRouter())
]
Не забудьте объявить AppStateProvider перед AppRouter, который мы обсудим позже. Пока что мы создадим очень простой экран для тестирования.
Бортовой экран
onboard_screen.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:temple/globals/providers/app_state_provider.dart';
class OnBoardScreen extends StatefulWidget {
const OnBoardScreen({Key? key}) : super(key: key);
@override
State<OnBoardScreen> createState() => _OnBoardScreenState();
}
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
// When user pressed skip/done button we'll finally set onboardCount integer
stateProvider.hasOnboarded();
// After that onboard state is done we'll go to homepage.
GoRouter.of(context).go("/");
}
class _OnBoardScreenState extends State<OnBoardScreen> {
@override
Widget build(BuildContext context) {
final appStateProvider = Provider.of<AppStateProvider>(context);
return Scaffold(
body: Center(
child: Column(
children: [
const Text("This is Onboard Screen"),
ElevatedButton(
onPressed: () => onSubmitDone(appStateProvider, context),
child: const Text("Done/Skip"))
],
)),
);
}
}
В этом файле был создан класс виджета с состоянием. Главное, на что следует обратить внимание, это функция onSubmitDone(). Эта функция будет вызываться, когда пользователь либо нажмет кнопку skip во время onboarding, либо кнопку done, когда onboarding будет завершен. Здесь вызывается метод hasOnboarded, который мы определили ранее в провайдере, что приводит все в движение.
После этого наш маршрутизатор приведет нас на домашнюю страницу.
Теперь мы закончили! Или закончили? Мы все еще не ввели инструкции по перенаправлению в наш маршрутизатор. Следовательно, давайте внесем некоторые изменения в наш маршрутизатор приложений.
Перенаправление маршрутизатора
app_router.dart
// Packages
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
//Custom files
import 'package:temple/screens/home/home.dart';
import 'utils/router_utils.dart';
import 'package:temple/screens/onboard/onboard_screen.dart';
import 'package:temple/globals/providers/app_state_provider.dart';
class AppRouter {
//=======================change #1 start ===========/
AppRouter({
required this.appStateProvider,
required this.prefs,
});
AppStateProvider appStateProvider;
late SharedPreferences prefs;
//=======================change #1 end===========/
get router => _router;
// change final to late final to use prefs inside redirect.
late final _router = GoRouter(
refreshListenable:
appStateProvider, //=======================change #2===========/
initialLocation: "/",
routes: [
GoRoute(
path: APP_PAGE.home.routePath,
name: APP_PAGE.home.routeName,
builder: (context, state) => const Home(),
),
// Add the onboard Screen
//=======================change #3 start===========/
GoRoute(
path: APP_PAGE.onboard.routePath,
name: APP_PAGE.onboard.routeName,
builder: (context, state) => const OnBoardScreen()),
//=======================change #3 end===========/
],
redirect: (state) {
//=======================change #4 start===========/
// define the named path of onboard screen
final String onboardPath =
state.namedLocation(APP_PAGE.onboard.routeName); //#4.1
// Checking if current path is onboarding or not
bool isOnboarding = state.subloc == onboardPath; //#4.2
// check if sharedPref as onBoardCount key or not
//if is does then we won't onboard else we will
bool toOnboard =
prefs.containsKey('onBoardCount') ? false : true; //#4.3
//#4.4
if (toOnboard) {
// return null if the current location is already OnboardScreen to prevent looping
return isOnboarding ? null : onboardPath;
}
// returning null will tell router to don't mind redirect section
return null; //#4.5
//=======================change #4 end===========/
});
}
Давайте пройдемся по изменениям, которые мы внесли.
-
Мы создали два файла классов: appStateRouter и prefs. Экземпляр prefs SharedPrefences необходим для проверки того, были ли мы уже на борту или нет, основываясь на существовании целого числа onboard count. AppStateProvider будет предоставлять все изменения, которые имеют значение для маршрутизатора.
-
Свойство маршрутизатора refreshListenableTo установлено для прослушивания изменений от appStateProvider.
-
Мы добавили маршрут OnBoardScreen в список маршрутов.
-
Здесь мы,
- Сначала создали именованное местоположение для бортового экрана.
- Затем isOnboarding проверяет, направляется ли текущий маршрут (state.subloc) к бортовому экрану или нет.
- Если в локальном хранилище уже есть целое число onBoardCount, то мы не будем выполнять onboarding, иначе будем.
- В зависимости от значения toOnboard мы вернем либо null, либо onBoardPath для перенаправления. Мы проверили, идет ли текущий маршрут к onBoardScreen с помощью функции isOboarding, и если да, то вернули null. Нам необходимо это сделать, если мы этого не сделаем, то маршрутизатор войдет в цикл и вызовет ошибку.
- И наконец, если нам не нужно никуда перенаправлять, то возвращаем null, который говорит маршрутизатору, что пока игнорируем перенаправление.
(PS: Я не упомянул об изменениях в импорте).
Прокси-провайдер для маршрутизатора
Итак, на этом этапе, вероятно, ваш провайдер кричит об ошибках красным цветом. Это результат того, что мы объявили два поля в AppRouter, но не указали их значения в нашем app.dart. Итак, давайте это исправим.
app.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:temple/globals/providers/app_state_provider.dart';
import 'package:temple/globals/settings/router/app_router.dart';
class MyApp extends StatefulWidget {
// Declared fields prefs which we will pass to the router class
//=======================change #1==========/
SharedPreferences prefs;
MyApp({required this.prefs, Key? key}) : super(key: key);
//=======================change #1 end===========/
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppStateProvider()),
//=======================change #2==========/
// Remove previous Provider call and create new proxyprovider that depends on AppStateProvider
ProxyProvider<AppStateProvider, AppRouter>(
update: (context, appStateProvider, _) => AppRouter(
appStateProvider: appStateProvider, prefs: widget.prefs))
],
//=======================change #2 end==========/
child: Builder(
builder: ((context) {
final GoRouter router = Provider.of<AppRouter>(context).router;
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate);
}),
),
);
}
}
- Сначала создадим поле prefs, которое нужно передать в качестве значения в наш файл main.dart, где мы вызываем класс MyApp.
- Мы создадим провайдер Proxy AppRouter, который будет зависеть от AppStateProvider. Прокси-провайдер — не единственный способ передачи значения.
main.dart
Теперь появилось еще одно красное предупреждение, потому что мы еще не передали наше поле prefs в файл main.dart.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:temple/app.dart';
//=======================change #1==========/
// make app an async funtion to instantiate shared preferences
void main() async {
// concrete binding for applications based on the Widgets framewor
WidgetsFlutterBinding.ensureInitialized();
//=======================change #2==========/
// Instantiate shared pref
SharedPreferences prefs = await SharedPreferences.getInstance();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.black38),
);
//=======================change #3==========/
// Pass prefs as value in MyApp
runApp(MyApp(prefs: prefs));
}
Здесь мы просто преобразовали метод main() в асинхронный метод. Мы сделали это для инстанцирования общих предпочтений, которые затем передаются в качестве значения для поля prefs класса MyApp. Теперь, когда вы запустите приложение, оно должно работать так, как задумано.
UI и анимация на экране
Теперь, когда мы заставили функциональность работать. Давайте сделаем что-нибудь с самим бортовым экраном. Вы можете скачать следующие изображения или использовать свои собственные. Я создал их на canva бесплатно.
onboard_screen.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:temple/globals/providers/app_state_provider.dart';
class OnBoardScreen extends StatefulWidget {
const OnBoardScreen({Key? key}) : super(key: key);
@override
State<OnBoardScreen> createState() => _OnBoardScreenState();
}
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
// When user pressed skip/done button we'll finally set onboardCount integer
stateProvider.hasOnboarded();
// After that onboard state is done we'll go to homepage.
GoRouter.of(context).go("/");
}
class _OnBoardScreenState extends State<OnBoardScreen> {
// Create a private index to track image index
int _currentImgIndex = 0; // #1
// Create list with images to use while onboarding
// #2
final onBoardScreenImages = [
"assets/onboard/FindTemples.png",
"assets/onboard/FindVenues.png",
"assets/onboard/FindTemples.png",
"assets/onboard/FindVenues.png",
];
// Function to display next image in the list when next button is clicked
// #4
void nextImage() {
if (_currentImgIndex < onBoardScreenImages.length - 1) {
setState(() => _currentImgIndex += 1);
}
}
// Function to display previous image in the list when previous button is clicked
// #3
void prevImage() {
if (_currentImgIndex > 0) {
setState(() => _currentImgIndex -= 1);
}
}
@override
Widget build(BuildContext context) {
final appStateProvider = Provider.of<AppStateProvider>(context);
return Scaffold(
body: SafeArea(
child: Container(
color: const Color.fromARGB(255, 255, 209, 166),
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
// Animated switcher class to animated between images
// #4
AnimatedSwitcher(
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeOut,
transitionBuilder: ((child, animation) =>
ScaleTransition(scale: animation, child: child)),
duration: const Duration(milliseconds: 800),
child: Image.asset(
onBoardScreenImages[_currentImgIndex],
height: MediaQuery.of(context).size.height * 0.8,
width: double.infinity,
// Key is needed since widget type is same i.e Image
key: ValueKey<int>(_currentImgIndex),
),
),
// Container to that contains set butotns
// #5
Container(
color: Colors.black26,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
// Change visibility by currentImgIndex
// #6
onPressed: prevImage,
icon: _currentImgIndex == 0
? const Icon(null)
: const Icon(Icons.arrow_back),
),
IconButton(
// Change visibility by currentImgIndex
// #7
onPressed: _currentImgIndex ==
onBoardScreenImages.length - 1
? () =>
onSubmitDone(appStateProvider, context)
: nextImage,
icon: _currentImgIndex ==
onBoardScreenImages.length - 1
? const Icon(Icons.done)
: const Icon(Icons.arrow_forward),
)
],
))
],
))));
}
}
Я знаю, что это слишком много кода. Поэтому давайте пройдемся по ним по кусочку за раз.
// Create a private index to track image index
int _currentImgIndex = 0; // #1
// Create list with images to use while onboarding
// #2
final onBoardScreenImages = [
"assets/onboard/FindTemples.png",
"assets/onboard/FindVenues.png",
"assets/onboard/FindTemples.png",
"assets/onboard/FindVenues.png",
];
- Для отслеживания изображений создается приватная переменная _currentIndex.
- Изображения в списке onBoardScreenImages будут отображаться на экране в соответствии с _currentIndex.
// Function to display next image in the list when next button is clicked
// #1
void nextImage() {
if (_currentImgIndex < onBoardScreenImages.length - 1) {
setState(() => _currentImgIndex += 1);
}
}
// Function to display previous image in the list when previous button is clicked
// #2
void prevImage() {
if (_currentImgIndex > 0) {
setState(() => _currentImgIndex -= 1);
}
}
Эти функции будут отслеживать currentIndex, управляя локальным состоянием должным образом.
// Animated switcher class to animated between images
AnimatedSwitcher(
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeOut,
transitionBuilder: ((child, animation) =>
ScaleTransition(scale: animation, child: child)),
duration: const Duration(milliseconds: 800),
child: Image.asset(
onBoardScreenImages[_currentImgIndex],
height: MediaQuery.of(context).size.height * 0.8,
width: double.infinity,
// Key is needed since widget type is same i.e Image
key: ValueKey<int>(_currentImgIndex),
),
),
Мы используем AnimatedSwitcher для переключения между нашими виджетами изображений при использовании ScaleTransition. Кстати, если вы удалите свойство transitionBuilder, вы получите стандартный переход FadeTransition.
Container(
color: Colors.black26,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
// Change visibility by currentImgIndex
// #1
onPressed: prevImage,
icon: _currentImgIndex == 0
? const Icon(null)
: const Icon(Icons.arrow_back),
),
IconButton(
// Change visibility by currentImgIndex
// #2
onPressed: _currentImgIndex ==
onBoardScreenImages.length - 1
? () =>
onSubmitDone(appStateProvider, context)
: nextImage,
icon: _currentImgIndex ==
onBoardScreenImages.length - 1
? const Icon(Icons.done)
: const Icon(Icons.arrow_forward),
)
],
))
],
))
В этом контейнере мы переключаем внешний вид кнопки в зависимости от индекса.
- Стрелка назад исчезнет, если изображение будет первым в списке изображений.
- Стрелка вперед будет заменена значком done, если изображение является последним в списке изображений. Если это кнопка next, то при нажатии будет вызвана функция nextImage(). В то время как кнопка done вызовет функцию submitButton().
Домашнее задание
Проверьте, чему вы научились до сих пор, и как далеко вы можете зайти. Я не буду предоставлять исходный код для домашнего задания, пожалуйста, избегайте ада учебника, делая ошибки и исправляя их самостоятельно.
-
Когда вы сохраните и перезапустите приложение, вы, скорее всего, столкнетесь с ошибкой, связанной с изображением. Исправьте ее самостоятельно, мы уже делали это в предыдущей части этой серии.
-
Создайте кнопку пропуска.
- Кнопка пропуска должна быть текстовой.
- Расположите ее внизу по центру, но внутри контейнера других кнопок.
- Текст должен быть красного цвета.
- Реализуйте ту же логику, что и в кнопке отправки.
Если вы уже сделали эту часть, поделитесь скриншотом в разделе комментариев.
Резюме
В этом блоге мы создали экран onboard с помощью пакетов GoRouter, Shared Preferences и Provider. Вот что мы сделали вкратце:
- Мы создали файл router utils для эффективной работы.
- Мы также использовали MaterialApp.Router для реализации декларативной маршрутизации с помощью пакета GoRouter.
- Затем мы использовали пакет SharedPreferences для хранения информации на борту в локальном хранилище.
- С помощью класса AppStateProvider мы написали нашу бортовую логику и перенаправили наши маршруты внутрь класса AppRouter.
- Мы также создали простой бортовой экран с анимацией ScaleTransition.
Наша работа выглядит следующим образом:
Показать поддержку
В следующем блоге мы определим тему нашего приложения. Если вы хотите узнать больше о серии и ожиданиях, ознакомьтесь с вводной страницей серии.
Если у вас есть вопросы, пожалуйста, не стесняйтесь комментировать. Пожалуйста, ставьте лайк, добавляйте статью в закладки и делитесь ею со своими друзьями. Спасибо за ваше время и, пожалуйста, продолжайте поддерживать нас. Не забывайте следить за нами, чтобы получать уведомления сразу после загрузки.
Это Нибеш из Khadka’s Coding Lounge, фрилансерского агентства, которое занимается созданием сайтов и мобильных приложений.