نحوه استفاده از استریم ها و خدمات برای وضعیت فلاتر
در میان بسیاری از معماریهای مدیریت دولتی در فلاتر، ترکیب جریانهای دارت با کلاسهای تکتن (سرویسها) یک معماری غیرمحبوب و در عین حال آسان است.
در این مقاله، نحوه دستیابی به این ترکیب را برای حالت گسترده برنامه در Flutter تحلیل خواهیم کرد.
فهرست مطالب
نحوه ایجاد نمونه (یا خدمات) کلاس Singleton
نحوه دستکاری وضعیت (جریان ها) در خدمات
نحوه استفاده از Dart Stream در ابزارک های Flutter
چگونه یک سرویس را به دیگری وابسته کنیم
نحوه بهبود استریم ها با کلاس ها و برنامه های گفت نی rxdart
نحوه به روز رسانی وضعیت در AppLifecycle Callbacks
App-wide State در فلاتر چیست؟
حالت گسترده برنامه شامل تمام متغیرهایی است که همزمان با چندین ویجت مرتبط هستند. منظور ما از حالت گسترده برنامه، حالتی نیست که به StatefulWidgets
متصل است. اینها حالت زودگذر هستند. به روز رسانی آنها نیاز به تماس های محلی یا محدوده ای با setState دارد.
در Flutter، حالت گسترده برنامه معمولاً یک مدیریت منطقی جداگانه از کد UI دارد. این منطق جدا شده معماری مدیریت حالت نامیده می شود. ما معماریهای مدیریت حالت بسیاری داریم که میتوانیم با استفاده از آنها، حالت گسترده برنامه را مهندسی کنیم. به عنوان مثال می توان به Provider ، InheritedWidget ، Riverpod ، Bloc ، Redux ، Stacked و غیره اشاره کرد. هر یک از این معماری های مدیریت دولتی کارآمد، خوب و صاحب نظر هستند.
در حالی که انتخاب معماری شما می تواند بر اساس عوامل مختلف متفاوت باشد، معماری زیر را در پروژه های خود در نظر بگیرید. این شامل استفاده از جریان ها و سرویس های Dart (کلاس های تک نفره) برای پیگیری وضعیت برنامه شما است.
استریم در دارت چیست؟
یک جریان به طور مداوم مقادیری را منتشر می کند. شما می توانید به یک جریان گوش دهید و به طور مداوم مقادیر جدیدی را هنگام انتشار دریافت کنید. Streams در Dart معادل Observable
در جاوا اسکریپت است.
در دارت، استریم ها با آینده متفاوت هستند. تفاوت در این است که در حالی که آینده به یک مقدار معین می شود، یک جریان به طور مداوم مقادیر مختلفی را در طول عمر خود منتشر می کند.
فرض کنید یک جریان counter
داریم که تعدادی اعداد صحیح فعلی را ردیابی می کند. این تعداد می تواند افزایش یا کاهش یابد. برای استفاده از مقادیر منتشر شده توسط این جریان counter
، به counter
گوش می دهید. Listening به معنای فراخوانی روش .listen
در جریان و مدیریت مقدار منتشر شده است.
counter.listen(( int value) => print ( 'Got $value .' ));
نحوه ایجاد یک جریان در دارت
کلاس Stream
دارای چندین سازنده کارخانه است. آنها به شما اجازه می دهند تا جریان های مختلفی را برای اهداف مختلف ایجاد کنید. آنها عبارتند از:
Stream.empty
Stream.value
Stream.error
Stream.fromFuture
Stream.fromFutures
Stream.fromIterable
Stream.multi
Stream.periodic
Stream.eventTransformed
هر سازنده همانطور که از نامش پیداست هدف خاصی را انجام می دهد.
یکی دیگر از تکنیک های ایجاد یک Stream
، به دست آوردن آن از یک StreamController
است. شما باید StreamController
را خودتان بسازید. مزیت انجام این کار این است که کنترلر به شما اجازه می دهد مقادیری را به آن اضافه کنید . وقتی مقادیری را به کنترلر اضافه می کنید، به شنوندگان جریان آن ارسال می شود.
import 'dart:async' ; void main() { final counterCtrl = StreamController< int >(); counterCtrl.stream.listen( print ); counterCtrl.add( 1 ); // prints 1 }
مشکل StreamController
پیشفرض از کتابخانه dart:async
این است که فقط به یک شنونده اجازه میدهد. یکپارچه است. اگر سعی کنید شنونده دیگری را به این جریان دریافت شده از StreamController
متصل کنید، خطای "حالت بد" ایجاد می کند.
این مشکل توسط کلاس BehaviorSubject
از بسته rxdart
حل شده است. از نظر فنی، BehaviorSubject
یک StreamController
است. تفاوت این است که دارای ویژگی های بیشتری مانند:
به چندین شنونده اجازه می دهد (بسیار مهم).
آخرین مقدار یا خطای منتشر شده را در حافظه پنهان ذخیره می کند.
آخرین مقدار/خطای حافظه پنهان را برای شنونده جدید به محض اینکه به تازگی مشترک شود منتشر می کند.
به شما این امکان را می دهد که به طور همزمان مقدار فعلی (یا آخرین صادر شده) را از آن بخوانید.
به شما امکان می دهد اگر هنوز شنونده ای ندارد به آن مقادیر اضافه کنید ( StreamController
پیش فرض این اجازه را نمی دهد).
بسته rxdart
قابلیت های جریان دارت را گسترش می دهد. به عنوان مثال، BehaviorSubject
را در اختیار شما قرار می دهد. همچنین، کلاسها و برنامههای گفت نی را در معرض دید قرار میدهد که امکان دستکاری بیشتر جریان را فراهم میکنند. برای استفاده از بسته rxdart
، آن را با استفاده از دستور زیر به وابستگی های پروژه خود از pub اضافه کنید:
flutter pub add rxdart
سپس آن را در فایل های Dart پروژه خود وارد کنید. از آنجا، میتوانید BehaviorSubject
( StreamController
قویتر) ایجاد کنید که به چندین شنونده اجازه میدهد و در عین حال به شما امکان میدهد آنها را کنترل کنید (به جریانها مقادیر اضافه کنید).
import 'package:rxdart/rxdart.dart' ; void main() { // Create a BehaviorSubject. // // Asides from creating the BehaviorSubject, we can also // immediately add a value to it using Dart's cascade operator. final counterBS = BehaviorSubject< int >()..add( 0 ); counterBS.stream.listen( print ); // prints 0 counterBS.stream.listen( print ); // prints 0 counterBS.add( 1 ); // prints 1 twice }
اکنون که میتوانیم استریمهایی ایجاد کنیم (و به آنها گوش دهیم)، دقیقاً به همان جریانها نیاز داریم تا در هر قسمت از برنامههای Flutter ما در دسترس باشد.
برای اطمینان از اینکه همان نمونه جریانهایی است که بخشهای مختلف برنامههای Flutter ما به آن دسترسی دارند، میتوانیم جریانها را از نمونههای کلاس singleton که در پروژه ایجاد میکنیم، نمایش دهیم.
نحوه ایجاد نمونه (یا خدمات) کلاس Singleton
وقتی به چیزی تک تن می گویند یعنی فقط یکی از آن وجود دارد. به عنوان مثال، می توان بيان کرد خورشید یک ستاره تک قلو است زیرا ما فقط یک خورشید داریم.
وقتی صحبت از برنامه نویسی به میان می آید، زمانی که به همان کپی از یک شی در همه جا نیاز داریم، از یک سینگلتون استفاده می کنیم. در حال حاضر، آپشن های static
یک کلاس برای هر نمونه از آن کلاس تکتنه است. هنگامی که یک فیلد یا روش را به عنوان static
اعلام می کنید، به موتور زمان اجرا می گویید که همیشه از همان آیتم ایستا دوباره استفاده کند.
این توضیح می دهد که چرا از خواص static
به عنوان ثابت استفاده می شود. این دلیل دیگری است که ما از آنها بدون نمونه سازی یک شی استفاده می کنیم. علاوه بر این، در Flutter، ما به طور معمول از خواص استاتیک به عنوان وسیله ای برای به دست آوردن نمونه های جدید یا موجود از یک کلاس استفاده می کنیم. برای مثال، بسیاری از کلاسهای Flutter ( MediaQuery
، Navigator
، ThemeData
، و غیره) دارای یک متد .of
برای به دست آوردن نمونههای خود هستند.
در این معماری جریان ها و سرویس ها، ما تنها یک نمونه از یک کلاس را با کلمه کلیدی static
نمایش می دهیم. در همان زمان، سازنده کلاس را مخفی می کنیم. مخفی کردن سازنده تضمین می کند که هیچ کد Dart دیگری خارج از فایل Dart نمی تواند نمونه دیگری از همان کلاس ایجاد کند. با انجام این کار، نمونه به صورت تکی حفظ می شود.
طبق قراردادهای رایج، می توانیم این کلاس را یک سرویس بنامیم. هر فایل Dart دیگری در پروژه می تواند به جریان (های) در معرض نمایش از کلاس سرویس گوش دهد و همیشه مقادیر به روز شده را دریافت کند.
خدمات در اینجا دارندگان ایالت در سطح برنامه هستند. هر سرویس یک ظرف منطقی از ویژگی های مرتبط است. در هر بخش دیگری از کد، از طریق این سرویسها، میتوانیم به متغیرهای حالت در سطح برنامه (در مورد ما، جریانها) دسترسی داشته باشیم. در یک برنامه تولیدی، میتوانیم یک سرویس احراز هویت، یکی برای اعلانها، دیگری برای فایلها و غیره داشته باشیم.
برای داشتن یک سرویس در دسترس در سراسر برنامه (کلاس تک نفره) با یک جریان در آن:
یک کلاس خدمات ایجاد کنید.
یک سازنده خصوصی ایجاد کنید (به طوری که هیچ کد دارت دیگری خارج از کلاس نتواند آن را نمونه سازی کند).
یک نمونه خصوصی ثابت از همان کلاس ایجاد کنید.
این نمونه خصوصی را بهعنوان تکنفره افشا کنید.
یک BehaviorSubject
خصوصی در آن کلاس ایجاد کنید.
جریان BehaviorSubject
را به عنوان یک دریافت کننده استاتیک عمومی از کلاس در معرض دید قرار دهید.
/* In counter_service.dart file */ import 'package:rxdart/rxdart.dart' ; // 1. Create a class // // The class name with "Service" appended to it indicates // that it is an app-wide state object. class CounterService { // 2. Create a private constructor. // // This "just-underscore" constructor works. If we want, we could // still add a name after the underscore. The main thing is that // underscore makes the constructor to be a private one. CounterService._(); // 3. Create a static private instance. // // Prefixing underscore (_) to the variable name makes it private. // By being private, no other Dart code outside this file can directly // access it. static final _instance = CounterService._(); // 4. Expose this private instance as the singleton. static CounterService get instance => _instance; // 5. Create a private BehaviorSubject. final _counterBS = BehaviorSubject< int >()..add( 0 ); // 6. Expose the BehaviorSubject's Stream. Stream< int > get countStream => _counterBS.stream; // Also, if need be, expose the BehaviorSubject's current as a getter. int get currentCount => _counterBS.value; } /* In any other Dart file in the project */ import 'counter_service.dart' // Attach a listener to the stream CounterService.instance.countStream.listen((count) { // Use the count as use wish. Code you write within this // listener's block will be called whenever count is // update/re-emitted. print (count); // prints 0 }); // Read the current stream value just once without subscribing print (CounterService.instance.currentCount); // prints 0
نحوه دستکاری وضعیت (جریان ها) در خدمات
اغلب اوقات، هر سرویس دارای چندین جریان است. این همان چیزی است که پیش بینی می شود، از آنجایی که برای یک ویژگی حالت منطقی معین، متغیرهای متعددی بر آن تأثیر می گذارند. پس ، در صورت لزوم، از اعلام چند BehaviorSubject
(در حین افشای جریانهای آنها) در یک کلاس خدمات تردید نکنید.
برای هر جریان، می خواهید داده های آن را کنترل کنید. به همین دلیل است که از BehaviorSubject
استفاده می کنیم تا در مواقعی که نیاز به به روز رسانی حالت وجود دارد، بتوانیم مقادیری را به آن اضافه کنیم.
رویدادهای مختلف (چه از طرف کاربر یا سرورهای شما) می تواند دلیل چنین به روز رسانی های حالت باشد. میخواهید هر زمان که این رویدادها رخ میدهند، بهروزرسانیهای وضعیت را فعال کنید (یا مقادیری به جریانها اضافه کنید).
شما همیشه میتوانید باطن خود را نظرسنجی کنید و در صورت وقوع هر رویدادی، تغییراتی را در جریانهای خود ایجاد کنید. همچنین می توانید مقادیری را بر اساس تغییرات در سرویس های دیگر منتشر کنید. علاوه بر این، در صورت نیاز، سرویسها باید روشهای مربوطه را نیز در معرض نمایش بگذارند که جریانهای آنها را بهروزرسانی میکند. به نوبه خود، سایر بخشهای برنامه میتوانند این روشها را فراخوانی کرده و تغییرات را ایجاد کنند. مزیت آشکار این است که هر شنونده به ترتیب مقدار جریان جدیدی را که برای آنها منتشر می شود دریافت می کند.
/* In counter_service.dart file */ import 'package:rxdart/rxdart.dart' ; class CounterService { CounterService._(); static final _instance = CounterService._(); static CounterService get instance => _instance; final _counterBS = BehaviorSubject< int >()..add( 0 ); Stream< int > get countStream => _counterBS.stream; int get currentCount => _counterBS.value; // Incrementing/Decrementing the counter will trigger state updates. void incrementCount() => _counterBS.add(currentCount + 1 ); void decrementCount() => _counterBS.add(currentCount - 1 ); } /* In another Dart file in the project */ import 'counter_service.dart' void main() { final service = CounterService.instance; service.countStream.listen( print ); // prints 0 service.incrementCount(); // causes 1 to be printed service.decrementCount(); // causes 0 to be printed }
برای مثال ملموس تر، فرض کنید یک AuthenticationService
داریم. مقداری _userBS
اعلام میکند و جریان currentUser
را با نوع Stream<User?>
در معرض نمایش میگذارد، در صورت احراز هویت کاربر معتبر یا در صورت خروج از سیستم null
خواهد بود. این سرویس احراز هویت به طور طبیعی دارای signIn
و signOut
است که هر دو می توانند مقادیری را به _userBS
اضافه کنند. صفحههای ثبت نام و ورود به سیستم میتوانند هر یک از آنها تماس signIn
در حالی که دکمههای "تغییر حساب" و "خروج از سیستم" میتوانند هر تماس را signOut
.
/* In user.dart */ // A simple user with only email and username for demo purposes. // Your User model/schema would have more properties. class User { final String email; final String username; const User( this .email, this .username); } /* In authentication_service.dart */ import 'package:rxdart/rxdart.dart' ; import 'user.dart' ; class AuthenticationService { AuthenticationService._(); static final _instance = AuthenticationService._(); static AuthenticationService instance => _instance; // User BehaviorSubject and its stream. final _userBS = BehaviorSubject<User?>()..add( null ); Stream<User?> get currentUser => _userBS.stream; // signIn adds a new User to the stream. void signIn( String email, String username}) { _userBS.add(User(email, username)); } // signOut sets the currentUser as null void signOut() => _userBS.add( null ); // signIn and signOut methods that tamper the state could do other // actions like recording analytics or carrying out navigation. // Also, they could do some validation or run some checks before // emitting values. The idea is that you get comfortable with // updating the values of BehaviorSubject (hence emitting streams) // from controlled methods within the service. }
یکی دیگر از نقاط دستکاری حالت در راه اندازی سرویس ها است. برخی از جریان ها ممکن است قبل از استفاده از آنها، یک اولیه ساز ناهمزمان را تضمین کنند. میتوانید متدهای init
را در سرویسها تعریف کنید و قبل از فراخوانی runApp
در بالاترین متد اصلی در Flutter، متدها را فراخوانی کنید.
روشهای init
ممکن است مقادیر ذخیرهشده «localStorage» از اجرای برنامههای قبلی باشند. آنها می توانند تماس های API برقرار کنند، مجوزها را تحلیل کنند یا شنوندگان EventChannel را راه اندازی کنند. هنگامی که آنها را قبل از runApp
فرا میخوانید، حتماً قبل از راهاندازی سرویسها از WidgetsFlutterBinding
ensureInitialized()
تماس بگیرید. این امر مخصوصاً در صورتی اجباری است که هر یک از کدهای init
سرویس به یک PlatformChannel
دسترسی داشته باشد.
/* authentication_service.dart */ // ... imports class AuthenticationService { // ... other code // initialize the service and carry-out other setups if need be. Future< void > init() async => _userBS.add( await _fetchSavedUser()); } /* main.dart */ import 'package:flutter/material.dart' ; import 'authentication_service.dart' ; Future< void > main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize the service to be sure it is up and running before // launching the app. You could also initialize other services here. // Only do this if they are carrying out asynchronous executions, // and the results need to be ready before the UI launches. await AuthenticationService.instance.init(); runApp( const MyApp()); }
نحوه استفاده از Dart Stream در ابزارک های Flutter
Flutter با ویجت StreamBuilder داخلی ارائه می شود. یک جریان و یک عملکرد سازنده می گیرد. این تابع سازنده یک BuildContext
و داده های عکس فوری در مورد جریان دریافت می کند. تابع همیشه باید یک ویجت را برگرداند.
هنگام ساختن رابطهای کاربری، میتوانید قسمتهای رابط کاربری را بپیچید که به جریانهای سراسر برنامه در StreamBuilders
وابسته هستند یا مقادیر منتشر شده از جریانهای سراسر برنامه را نمایش میدهند. به این ترتیب، هنگامی که جریان یک مقدار منتشر می کند، Flutter ویجت فرزند StreamBuilders
را با جدیدترین مقادیر بازسازی می کند.
import 'package:flutter/material.dart' ; import 'counter_service.dart' ; class CounterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder< int >( stream: CounterService.instance.countStream, // The stream to listen to initialData: CounterService.instance.currentCount, // Initial value builder: (context, snapshot) { // Check if the snapshot has data if (snapshot.hasData) { return Text( 'Counter: ${snapshot.data} ' , style: TextStyle(fontSize: 24 )); } else { // Handle any error or empty state return Text( 'Loading...' , style: TextStyle(fontSize: 24 )); } }, ); } }
StreamBuilders
ابزارهای عالی هستند. با این حال، مواقعی وجود دارد که استفاده از آنها مناسب نیست. به عنوان مثال:
وقتی یک صفحه رابط کاربری داده شده به جریانهای متعددی بستگی دارد که توسط سرویسهای یکسان یا متفاوت در معرض نمایش قرار میگیرند.
زمانی که میخواهید قبل از رندر کردن مقادیر جریان در رابط کاربری، مقداری محاسبات انجام دهید.
در این موارد، ما باید به طور جداگانه به جریانها در initState
گوش دهیم، مقادیر را از طریق تماسهای setState
تنظیم کنیم (برای بهروزرسانی رابط کاربری)، و StreamSubscriptions
را در روش dispose
از بین ببریم.
گوش دادن جداگانه به جریانها به ما امکان میدهد هر گونه سفارشیسازی را انجام دهیم یا زمانی که جریانها مقادیری را منتشر میکنند، دادهها را ادغام کنیم. علاوه بر این، از آنجایی که کدهای مرتبط با منطق را از روش ساخت خارج کردهایم، خواندن کد UI خود را آسانتر میکنیم. با این حال، ما باید این کار را فقط در صورت لزوم انجام دهیم: StreamBuilders
در بیشتر مواقع کافی هستند.
import 'dart:async' ; import 'package:flutter/material.dart' ; import 'counter_service.dart' ; class CounterStatefulWidget extends StatefulWidget { const CounterStatefulWidget({ super .key}); @override _CounterStatefulWidgetState createState() => _CounterStatefulWidgetState(); } class _CounterStatefulWidgetState extends State < CounterStatefulWidget > { late StreamSubscription< int > counterSub; int count = CounterService.instance.currentCount; @override void initState() { super .initState(); // Initialize the stream subscription counterSub = CounterService.instance.countStream.listen((count) { // Update state on new stream value setState(() => this .count = count); }); } @override void dispose() { // Dispose of the stream subscription to avoid memory leaks counterSub.cancel(); super .dispose(); } @override Widget build(BuildContext context) { return Text( 'Counter: $count ' , style: TextStyle(fontSize: 24 )); } }
مثال بالا گوش دادن و دفع خارج از روش ساخت را نشان می دهد. مثال مورد استفاده خوبی نیست که چه زمانی باید این کار را انجام دهید.
چگونه یک سرویس را به دیگری وابسته کنیم
در برنامه های پیچیده، داشتن سرویس هایی که به یکدیگر وابسته هستند معمول است. سرویس وابسته می تواند به جریان ها و روش های تماس سرویس مستقل گوش دهد. همچنین، سرویس وابسته می تواند سرویس مستقل را وارد کند و به آن ارجاع دهد، درست همانطور که در کد UI بالا انجام دادیم.
به عنوان مثال، اگر ما در حال ساخت یک برنامه تجارت الکترونیک هستیم، یک CartService
ممکن است به یک AuthenticationService
برای واکشی سبد خرید و سفارش برای کاربر وارد شده وابسته باشد. اگر کاربر از سیستم خارج شود، برخی از جریانهای currentUser
در AuthenticationService
null
منتشر میکنند. به نوبه خود، CartService
شنونده سبد خرید را به روز می کند. هنگامی که کاربر جدیدی وارد سیستم می شود، سبد خرید جدید را دریافت می کند.
import 'package:rxdart/rxdart.dart' ; import 'authentication_service.dart' ; // Item model representing a cart item. class CartItem { final String name; final int quantity; const CartItem( this .name, this .quantity); } // CartService to manage the user's shopping cart. class CartService { // ... // Dependency on AuthenticationService. final _auth = AuthenticationService.instance; final _cartItemsBS = BehaviorSubject< List <CartItem>>(); Stream< List <CartItem>> get cartStream => _cartItemsBS.stream; CartService() { // Listen to the currentUser stream in AuthenticationService. _auth.currentUserStream.listen((user) { if (user == null ) { // User signed out, clear the cart. _clearCart(); } else { // User signed in, fetch their cart. _fetchCartForUser(user.email); } }); } // Method to clear the cart (called on sign-out). void _clearCart() { _cartItemsBS.add([]); // Emit an empty list to clear the cart. } // Method to fetch the cart for a signed-in user (simulated). Future< void > _fetchCartForUser( String email) async { // ... } }
وقتی خدمات شما به یکدیگر وابسته است مراقب مشکلات وابستگی دایره ای باشید. وابستگی دایره ای زمانی رخ می دهد که دو سرویس به یکدیگر وابسته باشند. این سناریو معمولاً با رشد منطق تجاری اجتناب ناپذیر است.
وقتی با آن مواجه شدید، وضعیتی را که میخواهند مشترکاً به اشتراک بگذارند، به سرویس دیگری برسانید و این سرویس جدید را به سرویسهای دیگر وارد کنید. راه حل دیگر استفاده از کلمات کلیدی late
دارت در هنگام وارد کردن خدمات وابسته به یکدیگر است. همچنین میتوانید راههایی برای اطمینان از اینکه دسترسی متغیر در داخل توابع است و نه در برخی از اعلانهای سطح بالا پیدا کنید.
نحوه بهبود استریم ها با کلاس ها و برنامه های گفت نی rxdart
علاوه بر داشتن روشهای خدماتی که جریانها را بهروزرسانی میکنند، میتوانید با استفاده از کلاسها و برنامههای گفت نی rxdart
، استریمهای جدید یا بهبود یافتهای بر اساس موارد موجود داشته باشید.
یک کلاس مثال CombineLatestStream
است. برای برگرداندن یک جریان جدید که آخرین مقادیر ترکیبی جریان های منبع (بسته به ترکیب کننده اختیاری) را مجدداً منتشر می کند، چندین جریان و یک تابع ترکیب کننده لازم است.
import 'package:rxdart/rxdart.dart' ; class MultipliedCounterService { // ... final _counterBS = BehaviorSubject< int >()..add( 0 ); final _multiplierBS = BehaviorSubject< int >()..add( 2 ); Stream< int > get combinedStream => CombineLatestStream( [_counterBS.stream, _multiplierBS.stream], (values) => values[ 0 ] * values[ 1 ], ); void incrementCounter() => _counterBS.add(_counterBS.value + 1 ); void changeMultiplier( int mul) => _multiplierBS.add(mul); }
یکی دیگر از روش های جریان خوب، debounceTime
است. این یک برنامه گفت نی جریانی است که برای نادیده گرفتن انتشارات مکرر و پردازش آخرین مقدار پس از تأخیر (مانند هنگام جستجو) مفید است. انتشار تنها پس از مدت زمان تعیین شده و زمانی رخ می دهد که هیچ انتشار دیگری در بین آن زمان وجود نداشته باشد. با انتظار برای یک دوره عدم فعالیت قبل از انتشار آخرین مقدار، از تماس های بیش از حد API جلوگیری می کند.
import 'package:rxdart/rxdart.dart' ; class SearchService { // ... final _searchQueryBS = BehaviorSubject< String >()..add( '' ); // Stream with debouncing to emit values only after a // 300ms delay. For example: keystrokes will be bundled at once. Stream< String > get debouncedSearchQueryStream => _searchQueryBS.stream.debounceTime( Duration (milliseconds: 300 )); void updateSearchQuery( String query) => _searchQueryBS.add(query); }
بسته rxdart
کلاس ها و پسوندهای جریان بیشتری را ارائه می دهد که برای شما مفید خواهد بود، حتی اگر از این معماری استفاده نکنید. بعداً آنها را تحلیل کنید.
نحوه به روز رسانی وضعیت در AppLifecycle Callbacks
هنگامی که کاربر برنامه شما را کوچک می کند یا آن را ترک می کند و برمی گردد، ممکن است برخی از چیزهای خارجی که برای داده ها به آنها تکیه می کنید تغییر کرده باشند.
به عنوان مثال، هنگامی که از کاربر میخواهید مجوزی را صادر کند، سیستم عامل یک پنجره بازشو روی برنامه شما نمایش میدهد. به صورت برنامه ریزی شده، پنجره نمایش داده شده باعث می شود برنامه شما تمرکز خود را از دست بدهد یا به حالت پس زمینه برود. وقتی پنجره بازشو از بین رفت، برنامه شما دوباره تمرکز می کند و باید تشخیص دهید که آیا مجوزها را دریافت کرده اید یا خیر.
به همین ترتیب، اگر محتوای یک فهرست فایل اکسپلورر خاص را در برنامه خود مدیریت می کنید (مانند موسیقی تبدیل شده، اسناد رمزگذاری شده، گزارش تماس ها، و غیره)، وقتی برنامه شما در پس زمینه قرار می گیرد، ممکن است تغییراتی در آن فهرست توسط کاربر ایجاد شود. ، که ارزش شناسایی را هنگام بازگشت کاربر دارند.
گاهی اوقات، ممکن است بخواهید بدانید چه زمانی کاربر برای اهداف احراز هویت به برنامه شما بازمیگردد، مانند پایان دادن به یک جلسه در صورتی که برای مدت طولانی دور ماند و نیاز به احراز هویت مجدد داشت. در مواقع دیگر، ممکن است بخواهید محتوای برنامه را بازخوانی کنید تا کاربر را حفظ کنید، همانطور که می توانید در هنگام ساخت یک برنامه رسانه اجتماعی انجام دهید.
در همه این موارد، ما به روشی نیاز داریم تا از طریق برنامهریزی بدانیم چه زمانی برنامه ما پس از خروج کاربر به تمرکز کاربر برمیگردد. خوشبختانه، Flutter AppLifecycleState
و راهی برای واکنش به تغییرات ایجاد شده در آنها در اختیار ما قرار می دهد.
چرخه عمر یک برنامه به حالت های مختلف آن در حین اجرا اشاره دارد. در Flutter، AppLifecycleState
شامل جدا، از سر گرفته شده، غیرفعال، پنهان و مکث می شود. در موارد مثال بالا، هر زمان که کاربر به برنامه بازگردد، وضعیت چرخه عمر برنامه به AppLifecycleState.resumed
تبدیل می شود.
ما میتوانیم به تغییرات چرخه عمر واکنش نشان دهیم و زمانی که وضعیت خاصی رخ میدهد، روشهای خدمات خود را فراخوانی کنیم. برای گوش دادن به تغییرات چرخه حیات، کلاس سرویس شما باید Mixin WidgetsBindingObserver
را به اعلان خود اضافه کند. سپس باید didChangeAppLifecycleState
با یک callback لغو کنید. این فراخوان باید حالتهایی را که به آنها علاقهمند است کنترل کند.
import 'package:flutter/material.dart' ; class PermissionService with WidgetsBindingObserver { // ... Future< void > checkPermissions() async { // ... } @override Future< void > didChangeAppLifecycleState(AppLifecycleState state) async { if (state == AppLifecycleState.resumed) { await checkPermissions(); } // you can check for the other states too and handle as expected. } }
انعطاف پذیری در مدیریت دولتی
انتخاب ها و طعم های متعددی برای مدیریت دولتی در جامعه فلاتر وجود دارد. اغلب اوقات، ویژگی های یکسانی را می توان با هر مدیریت دولتی انتخابی ایجاد کرد.
با در نظر گرفتن این موضوع، با معماری های مدیریت حالت در Flutter انعطاف پذیر باشید. آنها قوانین سختی نیستند. خم شوید و با آنها بازی کنید تا با کیسهای اپلیکیشن منحصربهفرد شما مطابقت داشته باشد، زیرا در اینجا «یک اندازه متناسب با همه» وجود ندارد.
میتوانید با استریمها و سرویسها بازی کنید. برای بدست آوردن تک قلوها می توانید از getIt استفاده کنید. getIt
همچنین به شما این امکان را میدهد که تکتونهای محدودهدار را به دست آورید، یعنی تکتنهایی که به یک ناوبر یا بخشی منطقی از ویژگیها متصل شدهاند (برای مثال در یک جستجو).
شما همچنین می توانید این معماری را با سایر معماری ها ترکیب کنید. مانند اعلام و مدیریت جریانها همانطور که در اینجا توضیح داده شده است، اما به صورت ارائهدهنده یا کوبیت. یا آوردن ویژگی های معماری های دیگر در سرویس هایی که اعلام می کنید همانطور که در این مقاله توضیح داده شده است.
فقط مطمئن شوید که می دانید چه کاری انجام می دهید و می دانید که چگونه متغیرهای نشان دهنده وضعیت برنامه را هماهنگ کنید. ترجیحاً، معماری انتخابی خود را در پایگاه کد خود برای مراجعات بعدی مستند کنید.
خلاصه
به طور خلاصه، ما یک معماری کارآمد را برای مدیریت حالت گسترده برنامه در Flutter با استفاده از جریانهای Dart و خدمات singleton تحلیل کردهایم.
ما همچنین نحوه دستکاری جریان ها، نحوه استفاده از آنها در کدهای رابط کاربری، وابستگی سرویس ها به یکدیگر، بهبود جریان ها با استفاده از rxdart
و مدیریت تغییرات چرخه عمر برنامه را دیده ایم.
به یاد داشته باشید که مدیریت حالت در فلاتر انعطاف پذیر است و هیچ راه حلی برای همه مناسب نیست. انتخاب معماری مدیریت دولتی را متناسب با نیازهای برنامه خاص خود تنظیم کنید.
ارسال نظر