स्पंदन ऐप आर्किटेक्चर 101: वेनिला, स्कॉप्ड मॉडल, बीएलओसी


(मूल रूप से मध्यम पर प्रकाशित)


स्पंदन एक आधुनिक प्रतिक्रिया-शैली की रूपरेखा, समृद्ध विजेट संग्रह और टूलिंग प्रदान करता है, लेकिन ऐप आर्किटेक्चर के लिए एंड्रॉइड के गाइड के समान कुछ भी नहीं है।


वास्तव में, कोई भी अंतिम वास्तुकला नहीं है जो सभी संभावित आवश्यकताओं को पूरा करेगी, फिर भी इस तथ्य का सामना करना चाहिए कि हम जिन मोबाइल ऐप पर काम कर रहे हैं उनमें से कम से कम कुछ कार्यक्षमताएं हैं:


  1. नेटवर्क से / के लिए अनुरोध / अपलोड डेटा।
  2. नक्शा, रूपांतरण, डेटा तैयार करें और इसे उपयोगकर्ता को प्रस्तुत करें।
  3. डेटाबेस से / के लिए डेटा डालें / प्राप्त करें।

इसे ध्यान में रखते हुए मैंने एक नमूना ऐप बनाया है जो वास्तुकला के तीन अलग-अलग तरीकों का उपयोग करके ठीक उसी समस्या को हल कर रहा है।


उपयोगकर्ता को स्क्रीन के केंद्र में एक बटन "लोड उपयोगकर्ता डेटा" के साथ प्रस्तुत किया जाता है। जब उपयोगकर्ता बटन पर क्लिक करता है तो एसिंक्रोनस डेटा लोडिंग चालू हो जाती है और बटन को लोडिंग इंडिकेटर से बदल दिया जाता है। डेटा लोड होने के बाद लोडिंग इंडिकेटर को डेटा के साथ बदल दिया जाता है।


चलिए शुरू करते हैं।



डेटा


सादगी के उद्देश्य से मैंने Repository वर्ग बनाया है जिसमें getUser() विधि है जो एक अतुल्यकालिक नेटवर्क कॉल का अनुकरण करता है और Future<User> ऑब्जेक्ट को हार्डकोड किए गए मानों के साथ लौटाता है।


यदि आप डार्ट में फ्यूचर्स और अतुल्यकालिक प्रोग्रामिंग से परिचित नहीं हैं, तो आप इस ट्यूटोरियल का अनुसरण करके और डॉक पढ़कर इसके बारे में अधिक जान सकते हैं।


 class Repository { Future<User> getUser() async { await Future.delayed(Duration(seconds: 2)); return User(name: 'John', surname: 'Smith'); } } 

 class User { User({ @required this.name, @required this.surname, }); final String name; final String surname; } 

वैनीला


आधिकारिक फ़्लटर प्रलेखन को पढ़ने के बाद अधिकांश डेवलपर्स जिस तरह से करते हैं, उस तरीके से ऐप का निर्माण करें।


Navigator का उपयोग करके VanillaScreen स्क्रीन पर Navigator


 Navigator.push( context, MaterialPageRoute( builder: (context) => VanillaScreen(_repository), ), ); 

जैसा कि विजेट के जीवनकाल के दौरान हम StatefulWidget विस्तार करना चाहते हैं, विजेट की स्थिति कई बार बदल सकती है। स्टेटफुल विजेट लागू करने के लिए भी State क्लास होना आवश्यक है। फील्ड्स bool _isLoading और User _user _VanillaScreenState वर्ग में विजेट की स्थिति का प्रतिनिधित्व करते हैं। build(BuildContext context) विधि कहा जाता है से पहले दोनों फ़ील्ड प्रारंभ की हैं।


 class VanillaScreen extends StatefulWidget { VanillaScreen(this._repository); final Repository _repository; @override State<StatefulWidget> createState() => _VanillaScreenState(); } class _VanillaScreenState extends State<VanillaScreen> { bool _isLoading = false; User _user; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Vanilla'), ), body: SafeArea( child: _isLoading ? _buildLoading() : _buildBody(), ), ); } Widget _buildBody() { if (_user != null) { return _buildContent(); } else { return _buildInit(); } } Widget _buildInit() { return Center( child: RaisedButton( child: const Text('Load user data'), onPressed: () { setState(() { _isLoading = true; }); widget._repository.getUser().then((user) { setState(() { _user = user; _isLoading = false; }); }); }, ), ); } Widget _buildContent() { return Center( child: Text('Hello ${_user.name} ${_user.surname}'), ); } Widget _buildLoading() { return const Center( child: CircularProgressIndicator(), ); } } 

जब UI बनाने के लिए विजेट स्टेट ऑब्जेक्ट बनाया जाता है build(BuildContext context) विधि कहा जाता है। वर्तमान स्थिति का प्रतिनिधित्व करने के लिए बनाए जाने वाले विगेट्स के बारे में सभी निर्णय यूआई घोषणा कोड में किए गए हैं।


 body: SafeArea( child: _isLoading ? _buildLoading() : _buildBody(), ) 

उपयोगकर्ता द्वारा "लोड उपयोगकर्ता विवरण लोड करें" बटन पर क्लिक करने के बाद हम प्रगति संकेतक प्रदर्शित करते हैं।


 setState(() { _isLoading = true; }); 

कॉलिंग सेटस्टैट () इस रूपरेखा को सूचित करता है कि इस वस्तु की आंतरिक स्थिति इस तरह से बदल गई है जो इस उपप्रकार में उपयोगकर्ता इंटरफ़ेस को प्रभावित कर सकती है, जो इस राज्य वस्तु के लिए रूपरेखा का निर्माण करने का कारण बनता है।

इसका मतलब है कि कॉल करने के बाद setState() मेथड build(BuildContext context) विधि को फिर से फ्रेमवर्क द्वारा बुलाया जाता है और पूरे विजेट ट्री को फिर से बनाया जाता है । जैसा कि _isLoading अब true विधि पर सेट है _buildLoading() बजाय कहा जाता है और लोडिंग संकेतक स्क्रीन पर प्रदर्शित होता है। ठीक वैसा ही तब होता है जब हम getUser() और कॉल setState() से कॉलबैक को फिर से _isLoading करने के लिए _isLoading और _user फ़ील्ड्स को पुन: असाइन _user हैं।


 widget._repository.getUser().then((user) { setState(() { _user = user; _isLoading = false; }); }); 

पेशेवरों


  1. जानने और समझने में आसान।
  2. किसी तीसरे पक्ष के पुस्तकालयों की आवश्यकता नहीं है।

विपक्ष


  1. संपूर्ण विजेट ट्री को हर बार विजेट स्थिति परिवर्तन के साथ फिर से बनाया गया है।
  2. यह एकल जिम्मेदारी सिद्धांत को तोड़ रहा है। विजेट केवल यूआई के निर्माण के लिए जिम्मेदार नहीं है, यह डेटा लोडिंग, व्यावसायिक तर्क और राज्य प्रबंधन के लिए भी जिम्मेदार है।
  3. यूआई घोषणा कोड में वर्तमान स्थिति का प्रतिनिधित्व कैसे किया जाना चाहिए, इसके बारे में निर्णय। अगर हमारे पास थोड़ा और अधिक जटिल राज्य कोड होगा तो पठनीयता घट जाएगी।

स्कोप्ड मॉडल


स्कोप्ड मॉडल एक तृतीय-पक्ष पैकेज है जिसे फ़्लटर फ्रेमवर्क में शामिल नहीं किया गया है। इस प्रकार स्कोप्ड मॉडल के डेवलपर्स इसका वर्णन करते हैं:


उपयोगिताओं का एक सेट जो आपको एक माता-पिता विजेट से अपने वंशजों के लिए आसानी से एक डेटा मॉडल पास करने की अनुमति देता है। इसके अलावा, यह उन सभी बच्चों का भी पुनर्निर्माण करता है जो मॉडल के अपडेट होने पर उसका उपयोग करते हैं। यह लाइब्रेरी मूल रूप से फुचिया कोडबेस से निकाली गई थी।

चलो स्कोप्ड मॉडल का उपयोग करके एक ही स्क्रीन का निर्माण करें। सबसे पहले, हमें dependencies अनुभाग के तहत pubspec.yaml scoped_model निर्भरता जोड़कर scoped_model मॉडल पैकेज स्थापित करने की आवश्यकता है।


 scoped_model: ^1.0.1 

चलिए UserModelScreen विजेट पर एक नज़र डालते हैं और पिछले उदाहरण से इसकी तुलना करते हैं जो बिना स्कोप्ड मॉडल का उपयोग किए बनाया गया था। आइए UserModelScreen विजेट पर एक नज़र डालते हैं और पिछले उदाहरण से इसकी तुलना करते हैं जो बिना स्कोप्ड मॉडल का उपयोग किए बनाया गया था। जैसा कि हम अपने मॉडल को सभी विजेट के वंशजों को उपलब्ध कराना चाहते हैं, हमें इसे जेनेरिक स्कॉप्डमॉडल के साथ लपेटना चाहिए और एक विजेट और एक मॉडल प्रदान करना चाहिए।


 class UserModelScreen extends StatefulWidget { UserModelScreen(this._repository); final Repository _repository; @override State<StatefulWidget> createState() => _UserModelScreenState(); } class _UserModelScreenState extends State<UserModelScreen> { UserModel _userModel; @override void initState() { _userModel = UserModel(widget._repository); super.initState(); } @override Widget build(BuildContext context) { return ScopedModel( model: _userModel, child: Scaffold( appBar: AppBar( title: const Text('Scoped model'), ), body: SafeArea( child: ScopedModelDescendant<UserModel>( builder: (context, child, model) { if (model.isLoading) { return _buildLoading(); } else { if (model.user != null) { return _buildContent(model); } else { return _buildInit(model); } } }, ), ), ), ); } Widget _buildInit(UserModel userModel) { return Center( child: RaisedButton( child: const Text('Load user data'), onPressed: () { userModel.loadUserData(); }, ), ); } Widget _buildContent(UserModel userModel) { return Center( child: Text('Hello ${userModel.user.name} ${userModel.user.surname}'), ); } Widget _buildLoading() { return const Center( child: CircularProgressIndicator(), ); } } 

पिछले उदाहरण में पूरे विजेट ट्री को फिर से बनाया गया जब विजेट की स्थिति बदल गई। लेकिन क्या हमें वास्तव में पूरी स्क्रीन को फिर से बनाने की आवश्यकता है? उदाहरण के लिए AppBar को बिल्कुल भी नहीं बदलना चाहिए, इसलिए इसका पुनर्निर्माण करने का कोई मतलब नहीं है। आदर्श रूप में, हमें केवल उन विजेट्स को फिर से बनाना चाहिए जो अपडेट किए गए हैं। इसे हल करने के लिए स्कोप्ड मॉडल हमारी मदद कर सकता है।


ScopedModelDescendant<UserModel> विजेट विजेट ट्री में UserModel खोजने के लिए उपयोग किया जाता है। जब भी UserModel सूचित करता है कि परिवर्तन हुआ है, तो यह स्वचालित रूप से फिर से बनाया जाएगा।


एक और सुधार यह है कि UserModelScreen राज्य प्रबंधन और व्यावसायिक तर्क के लिए अब ज़िम्मेदार नहीं है।


चलिए UserModel कोड पर एक नज़र डालते हैं।


 class UserModel extends Model { UserModel(this._repository); final Repository _repository; bool _isLoading = false; User _user; User get user => _user; bool get isLoading => _isLoading; void loadUserData() { _isLoading = true; notifyListeners(); _repository.getUser().then((user) { _user = user; _isLoading = false; notifyListeners(); }); } static UserModel of(BuildContext context) => ScopedModel.of<UserModel>(context); } 

अब UserModel राज्य को रखता है और उसका प्रबंधन करता है। श्रोताओं को सूचित करने के लिए (और वंशजों के पुनर्निर्माण) के लिए कि परिवर्तन notifyListeners() विधि कहा जाना चाहिए।


पेशेवरों


  1. व्यापार तर्क, राज्य प्रबंधन और यूआई कोड जुदाई।
  2. सीखना आसान है।

    विपक्ष

  3. तीसरे पक्ष के पुस्तकालय की आवश्यकता है।
  4. जब मॉडल अधिक से अधिक जटिल हो जाता है, तो आपको notifyListeners() करना मुश्किल होता है जब आपको notifyListeners() को कॉल करना चाहिए।

ब्लॉक


BLoC ( B usiness Logic C ompords) Google डेवलपर्स द्वारा अनुशंसित एक पैटर्न है। यह राज्य परिवर्तनों का प्रबंधन और प्रचार करने के लिए धाराओं की कार्यक्षमता का लाभ उठाता है।


Android डेवलपर्स के लिए: आप Bloc ऑब्जेक्ट को ViewModel रूप में और LiveData रूप में LiveData रूप में LiveData । यह निम्न कोड को बहुत सरल बना देगा क्योंकि आप पहले से ही अवधारणाओं से परिचित हैं।


 class UserBloc { UserBloc(this._repository); final Repository _repository; final _userStreamController = StreamController<UserState>(); Stream<UserState> get user => _userStreamController.stream; void loadUserData() { _userStreamController.sink.add(UserState._userLoading()); _repository.getUser().then((user) { _userStreamController.sink.add(UserState._userData(user)); }); } void dispose() { _userStreamController.close(); } } class UserState { UserState(); factory UserState._userData(User user) = UserDataState; factory UserState._userLoading() = UserLoadingState; } class UserInitState extends UserState {} class UserLoadingState extends UserState {} class UserDataState extends UserState { UserDataState(this.user); final User user; } 

राज्य बदलने पर ग्राहकों को सूचित करने के लिए कोई अतिरिक्त विधि कॉल की आवश्यकता नहीं होती है।


मैंने स्क्रीन के संभावित राज्यों का प्रतिनिधित्व करने के लिए 3 कक्षाएं बनाई हैं:


उपयोगकर्ता को केंद्र में एक बटन के साथ एक स्क्रीन खुलने पर, उपयोगकर्ता के लिए उपयोगकर्ताइंस्टैट करें।


राज्य के लिए UserLoadingState , जब डेटा लोड किए जाने के दौरान लोडिंग संकेतक प्रदर्शित होता है।


जब डेटा लोड किया जाता है और स्क्रीन पर प्रदर्शित होता है, तो स्टेट के लिए UserDataState


इस तरह से राज्य परिवर्तन का प्रचार हमें यूआई घोषणा कोड में सभी तर्क से छुटकारा पाने की अनुमति देता है। _isLoading मॉडल के साथ उदाहरण में, हम अभी भी जाँच कर रहे थे कि क्या _isLoading यूआई घोषणा कोड में यह तय करने के लिए true है कि हमें किस विजेट को प्रस्तुत करना चाहिए। BLoC के मामले में हम स्क्रीन की स्थिति का प्रचार कर रहे हैं और UserBlocScreen विजेट की एकमात्र जिम्मेदारी यूआई को इस राज्य के लिए प्रस्तुत करना है।


 class UserBlocScreen extends StatefulWidget { UserBlocScreen(this._repository); final Repository _repository; @override State<StatefulWidget> createState() => _UserBlocScreenState(); } class _UserBlocScreenState extends State<UserBlocScreen> { UserBloc _userBloc; @override void initState() { _userBloc = UserBloc(widget._repository); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Bloc'), ), body: SafeArea( child: StreamBuilder<UserState>( stream: _userBloc.user, initialData: UserInitState(), builder: (context, snapshot) { if (snapshot.data is UserInitState) { return _buildInit(); } if (snapshot.data is UserDataState) { UserDataState state = snapshot.data; return _buildContent(state.user); } if (snapshot.data is UserLoadingState) { return _buildLoading(); } }, ), ), ); } Widget _buildInit() { return Center( child: RaisedButton( child: const Text('Load user data'), onPressed: () { _userBloc.loadUserData(); }, ), ); } Widget _buildContent(User user) { return Center( child: Text('Hello ${user.name} ${user.surname}'), ); } Widget _buildLoading() { return const Center( child: CircularProgressIndicator(), ); } @override void dispose() { _userBloc.dispose(); super.dispose(); } } 

पिछले उदाहरणों की तुलना में UserBlocScreen कोड और भी सरल हो गया। राज्य में होने वाले परिवर्तनों को सुनने के लिए हम StreamBuilder का उपयोग कर रहे हैं। StreamBuilder एक StatefulWidget जो स्ट्रीम के साथ सहभागिता के नवीनतम स्नैपशॉट के आधार पर खुद को बनाता है।


पेशेवरों


किसी तीसरे पक्ष के पुस्तकालयों की जरूरत नहीं।
व्यावसायिक तर्क, राज्य प्रबंधन और UI तर्क पृथक्करण।
यह प्रतिक्रियाशील है। notifyListeners() मॉडल के notifyListeners() के मामले में किसी अतिरिक्त कॉल की आवश्यकता नहीं है।


विपक्ष


स्ट्रीम या आरएक्सडार्ट के साथ काम करने का अनुभव आवश्यक है।



आप ऊपर दिए गए उदाहरणों के स्रोत कोड को इस github repo के रूप में देख सकते हैं।


मूलतः लेख मध्यम पर प्रकाशित हुआ है

Source: https://habr.com/ru/post/hi438524/


All Articles