برنامهها ممکن است دهها و یا شاید صدها مسیر را در طول استفاده از آنها طی کنند. مثلا کاربر وارد برنامه شود، فرم لاگین را ببیند، سپس وارد شود، به بخشهای مختلف برنامه مانند تنظیمات و… سر بزند. برخی از این مسیرها به عنوان مسیرهای سطح بالا (global) معنا پیدا می کنند. برای مثال، «/»، «پروفایل»، «مخاطب»، «social_feed» همه مسیرهای سطح بالای ممکن در برنامه شما هستند. حال تصور کنید که همه مسیرهای ممکن را در ویجت ناوبری سطح بالای خود تعریف کرده اید. جایی که لیستی از تمام مسیرهای ممکن در برنامه، آنجا قرار گرفته است. در نتیجه فهرست بسیار طولانی خواهد بود و بسیاری از این مسیرها بهتر است در داخل ویجت دیگری مدیریت شوند.
در این مثال، یک برنامه اینترنت اشیا را بررسی میکنیم که در آن جریان راه اندازی را برای یک لامپ بی سیم که با برنامه کنترل میشود در مد نظر است. این جریان راهاندازی شامل ۴ صفحه است: لامپهای نزدیک را پیدا کنید، لامپی را که میخواهید اضافه کنید انتخاب کنید، لامپ را اضافه کنید و سپس راهاندازی را کامل کنید. می توانید این رفتار را از ویجت ناوبری سطح بالای خود هماهنگ کنید. با این حال، منطقیتر است که یک ویجت ناوبر تودرتو در ویجت SetupFlow خود تعریف کنید و اجازه دهید ناوبر تودرتو مالکیت ۴ صفحه در جریان راهاندازی را در اختیار بگیرد. این تفویض ناوبری، کنترل محلی بیشتری را در اختیار ما قرار میدهد و این روند را به شدت تسهیل میکند که معمولاً هنگام توسعه نرم افزار اهمیت بسیار بالایی دارد. در هر صورت، ارتقای نظم در توسعهی یک نرافزار، مسیر توسعه را هموار میکند.
انیمیشن زیر رفتار برنامه را نشان می دهد:
در این آموزش، شما یک جریان راه اندازی اینترنت اشیاء چهار صفحه ای را پیاده سازی می کنید که ناوبری خود را در زیر ویجت Navigator سطح بالا حفظ می کند.
برای Navigation آماده شوید
این برنامه IoT دارای دو صفحه نمایش سطح بالا به همراه جریان راه اندازی است. این نام مسیرها را به عنوان const تعریف کنید تا بتوان به آنها در کد ارجاع داد.
const routeHome = '/';
const routeSettings = '/settings';
const routePrefixDeviceSetup = '/setup/';
const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage';
const routeDeviceSetupStartPage = 'find_devices';
const routeDeviceSetupSelectDevicePage = 'select_device';
const routeDeviceSetupConnectingPage = 'connecting';
const routeDeviceSetupFinishedPage = 'finished';
صفحه اصلی و تنظیمات با نام های ثابت ارجاع می شوند. با این حال، صفحات جریان راهاندازی از دو مسیر برای ایجاد نام مسیر خود استفاده میکنند: یک پیشوند /setup/ به دنبال نام صفحه خاص. با ترکیب این دو مسیر، Navigator شما میتواند تعیین کند که یک نام مسیر برای جریان راهاندازی در نظر گرفته شده است، بدون اینکه تمام صفحات جداگانه مرتبط با جریان راهاندازی را شناسایی کند.
Navigator سطح بالا مسئول شناسایی صفحات SetupFlow نیست. بنابراین، Navigator سطح بالای شما باید نام مسیر ورودی را برای شناسایی پیشوند جریان راه اندازی تجزیه کند. نیاز به تجزیه نام مسیر به این معنی است که نمی توانید از ویژگی routes در Navigator سطح بالای خود استفاده کنید. در عوض، شما باید یک تابع برای ویژگی onGenerateRoute ارائه دهید.
برای بازگرداندن ویجت مناسب برای هر یک از سه مسیر سطح بالا، onGenerateRoute را پیاده سازی کنید.
onGenerateRoute: (settings) {
late Widget page;
if (settings.name == routeHome) {
page = const HomeScreen();
} else if (settings.name == routeSettings) {
page = const SettingsScreen();
} else if (settings.name!.startsWith(routePrefixDeviceSetup)) {
final subRoute =
settings.name!.substring(routePrefixDeviceSetup.length);
page = SetupFlow(
setupPageRoute: subRoute,
);
} else {
throw Exception('Unknown route: ${settings.name}');
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
},
توجه داشته باشید که مسیرهای خانه و تنظیمات با نام دقیق مسیر مطابقت دارند. با این حال، شرایط مسیر SetupFlow فقط یک پیشوند را بررسی میکند. اگر نام مسیر حاوی پیشوند SetupFlow باشد، بقیه نام مسیر نادیده گرفته میشود و برای پردازش به ویجت SetupFlow ارسال میشود. این تقسیم نام مسیر چیزی است که به ناوبر سطح بالا اجازه می دهد تا نسبت به مسیرهای فرعی مختلف در SetupFlow نا مطلع باشد.
یک ویجت حالت دار به نام SetupFlow ایجاد کنید که نام مسیر را می پذیرد.
class SetupFlow extends StatefulWidget {
const SetupFlow({
Key? key,
required this.setupPageRoute,
}) : super(key: key);
final String setupPageRoute;
@override
SetupFlowState createState() => SetupFlowState();
}
class SetupFlowState extends State<SetupFlow> {
//...
}
نوار برنامه را برای SetupFlow نمایش دهید
در این قسمت از برنامه، ویجت SetupFlow یک نوار برنامه دائمی را نشان می دهد که در همه صفحات ظاهر می شود.
یک ویجت Scaffold را از متد build() ویجت SetupFlow خود برگردانید و ویجت AppBar مورد نظر را اضافه کنید.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildFlowAppBar(),
body: const SizedBox(),
);
}
PreferredSizeWidget _buildFlowAppBar() {
return AppBar(
title: const Text('Bulb Setup'),
);
}
نوار برنامه یک فلش عقب را نشان می دهد و با فشار دادن فلش عقب از جریان تنظیم خارج می شود. با این حال، خروج از جریان باعث می شود کاربر تمام پیشرفت خود را از دست بدهد. بنابراین، از کاربر خواسته می شود تا تأیید کند که آیا می خواهد از جریان راه اندازی خارج شود یا خیر.
در نتیجه باید از کاربر بخواهید خروج از SetupFlow را تأیید کند و اطمینان حاصل کنید که وقتی کاربر دکمه بازگشت سختافزار را در Android فشار میدهد، این درخواست ظاهر میشود.
Future<void> _onExitPressed() async {
final isConfirmed = await _isExitDesired();
if (isConfirmed && mounted) {
_exitSetup();
}
}
Future<bool> _isExitDesired() async {
return await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Are you sure?'),
content: const Text(
'If you exit device setup, your progress will be lost.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Leave'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Stay'),
),
],
);
}) ??
false;
}
void _exitSetup() {
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _isExitDesired,
child: Scaffold(
appBar: _buildFlowAppBar(),
body: const SizedBox(),
),
);
}
PreferredSizeWidget _buildFlowAppBar() {
return AppBar(
leading: IconButton(
onPressed: _onExitPressed,
icon: const Icon(Icons.chevron_left),
),
title: const Text('Bulb Setup'),
);
}
هنگامی که کاربر روی فلش برگشت در نوار برنامه ضربه میزند یا دکمه بازگشت را در اندروید فشار میدهد، یک گفتگوی هشدار ظاهر میشود تا تأیید کند که کاربر میخواهد از جریان تنظیم خارج شود. اگر کاربر ترک را فشار دهد، جریان تنظیم از پشته ناوبری سطح بالا خارج می شود. اگر کاربر Stay را فشار دهد، عمل نادیده گرفته می شود.
ممکن است متوجه شوید که Navigator.pop() توسط هر دو دکمه Leave و Stay فراخوانی می شود. برای واضح بودن، این اکشن pop() گفتگوی هشدار را از پشته ناوبری خارج می کند، نه جریان راه اندازی.
مسیرهای تو در تو را ایجاد کنید
وظیفه SetupFlow نمایش صفحه مناسب در جریان است.
ویجت Navigator را به SetupFlow اضافه کنید و ویژگی onGenerateRoute را پیاده سازی کنید.
final _navigatorKey = GlobalKey<NavigatorState>();
void _onDiscoveryComplete() {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage);
}
void _onDeviceSelected(String deviceId) {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage);
}
void _onConnectionEstablished() {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _isExitDesired,
child: Scaffold(
appBar: _buildFlowAppBar(),
body: Navigator(
key: _navigatorKey,
initialRoute: widget.setupPageRoute,
onGenerateRoute: _onGenerateRoute,
),
),
);
}
Route _onGenerateRoute(RouteSettings settings) {
late Widget page;
switch (settings.name) {
case routeDeviceSetupStartPage:
page = WaitingPage(
message: 'Searching for nearby bulb...',
onWaitComplete: _onDiscoveryComplete,
);
break;
case routeDeviceSetupSelectDevicePage:
page = SelectDevicePage(
onDeviceSelected: _onDeviceSelected,
);
break;
case routeDeviceSetupConnectingPage:
page = WaitingPage(
message: 'Connecting...',
onWaitComplete: _onConnectionEstablished,
);
break;
case routeDeviceSetupFinishedPage:
page = FinishedPage(
onFinishPressed: _exitSetup,
);
break;
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
}
تابع _onGenerateRoute مانند یک Navigator سطح بالا کار می کند. یک شی RouteSettings به تابع ارسال می شود که شامل نام مسیر است. بر اساس نام مسیر، یکی از چهار صفحه جریان برگردانده می شود.
صفحه اول که find_devices نام دارد، چند ثانیه صبر می کند تا اسکن شبکه را شبیه سازی کند. پس از دوره انتظار، صفحه تماس مجدد خود را فراخوانی می کند. در این مورد، آن callback _onDiscoveryComplete است. جریان راه اندازی تشخیص می دهد که وقتی کشف دستگاه کامل شد، صفحه انتخاب دستگاه باید نشان داده شود. بنابراین، در _onDiscoveryComplete، _navigatorKey به Navigator تودرتو دستور می دهد تا به صفحه select_device حرکت کند.
صفحه select_device از کاربر می خواهد که یک دستگاه را از لیست دستگاه های موجود انتخاب کند. در این دستور فقط یک دستگاه به کاربر ارائه می شود. هنگامی که کاربر روی یک دستگاه ضربه می زند، پاسخ به تماس onDeviceSelected فراخوانی می شود. جریان راه اندازی تشخیص می دهد که وقتی دستگاهی انتخاب می شود، صفحه اتصال باید نشان داده شود. بنابراین، در _onDeviceSelected، _navigatorKey به Navigator تودرتو دستور می دهد تا به صفحه “اتصال” حرکت کند.
صفحه اتصال مانند صفحه find_devices کار می کند. صفحه اتصال چند ثانیه منتظر می ماند و سپس تماس خود را فراخوانی می کند. در این حالت، callback _onConnectionEstablished است. جریان راه اندازی تشخیص می دهد که وقتی یک اتصال برقرار می شود، صفحه نهایی باید نشان داده شود. بنابراین، در _onConnectionEstablished، _navigatorKey به Navigator تودرتو دستور می دهد تا به صفحه تمام شده حرکت کند.
صفحه تمام شده دکمه Finish را در اختیار کاربر قرار می دهد. وقتی کاربر روی Finish ضربه میزند، فراخوانی _exitSetup فراخوانی میشود که کل جریان راهاندازی را از پشته Navigator سطح بالا خارج میکند و کاربر را به صفحه اصلی بازمیگرداند.
تبریک می گویم! شما ناوبری تودرتو را با چهار مسیر فرعی اجرا کردید.
موفق و پیروز باشید.
منبع: مستندات فلاتر