
सामान्य सिद्धांत
स्पंदन एक प्रतिक्रियाशील ढांचा है, और एक डेवलपर के लिए जो मूल विकास में माहिर है, उसका दर्शन असामान्य हो सकता है। इसलिए, हम एक छोटी समीक्षा के साथ शुरू करते हैं।
फ़्लटर पर उपयोगकर्ता इंटरफ़ेस, अधिकांश आधुनिक रूपरेखाओं में, घटकों (विजेट्स) के एक पेड़ के होते हैं। जब एक घटक बदलता है, तो यह और इसके सभी बाल घटक फिर से प्रस्तुत किए जाते हैं (आंतरिक अनुकूलन के साथ, जो नीचे वर्णित हैं)। जब डिस्प्ले विश्व स्तर पर बदलता है (उदाहरण के लिए, स्क्रीन को चालू करना), तो पूरे विजेट ट्री को फिर से परिभाषित किया गया है।
यह दृष्टिकोण अप्रभावी लग सकता है, लेकिन वास्तव में यह प्रोग्रामर को काम की गति पर नियंत्रण देता है। यदि आप इंटरफ़ेस को बिना आवश्यकता के उच्चतम स्तर पर अपडेट करते हैं, तो सब कुछ धीरे-धीरे काम करेगा, लेकिन विगेट्स के सही लेआउट के साथ, फ़्लटर पर एप्लिकेशन बहुत तेज़ हो सकते हैं।
स्पंदन में दो प्रकार के विजेट होते हैं - स्टेटलेस और स्टेटफुल। पूर्व (प्रतिक्रिया में शुद्ध अवयवों के अनुरूप) की कोई स्थिति नहीं है और पूरी तरह से उनके मापदंडों द्वारा वर्णित हैं। यदि प्रदर्शन की स्थिति नहीं बदलती (कहते हैं, उस क्षेत्र का आकार जिसमें विजेट प्रदर्शित किया जाना चाहिए) और उसके मापदंडों, सिस्टम विजेट के पहले बनाए गए दृश्य प्रतिनिधित्व का पुन: उपयोग करता है, इसलिए स्टेटलेस विजेट का उपयोग करने से प्रदर्शन पर अच्छा प्रभाव पड़ता है। उसी समय, वैसे भी, हर बार विजेट के फिर से तैयार होने पर, एक नई वस्तु औपचारिक रूप से बनाई जाती है और निर्माणकर्ता लॉन्च किया जाता है।
स्टेटफुल विजेट्स रेंडरिंग के बीच कुछ स्थिति बनाए रखते हैं। ऐसा करने के लिए, उन्हें दो वर्गों द्वारा वर्णित किया गया है। कक्षाओं के पहले, विजेट ही, उन वस्तुओं का वर्णन करता है जो प्रत्येक प्रतिपादन के दौरान बनाई जाती हैं। दूसरा वर्ग विजेट की स्थिति का वर्णन करता है और इसकी वस्तुओं को बनाए गए विजेट ऑब्जेक्ट में स्थानांतरित कर दिया जाता है। स्टेटफुल स्टेट विजेट्स इंटरफ़ेस रिड्रेसिंग का एक प्रमुख स्रोत हैं। ऐसा करने के लिए, आपको कॉल के अंदर इसके गुणों को सेटस्टेट विधि में बदलने की आवश्यकता है। इस प्रकार, कई अन्य रूपरेखाओं के विपरीत, फ़्लटर में निहित राज्य ट्रैकिंग नहीं है - सेटस्टैट विधि के बाहर विजेट के गुणों में कोई भी बदलाव इंटरफ़ेस को फिर से शुरू करने का कारण नहीं बनता है।
अब, मूल बातों का वर्णन करने के बाद, आप एक साधारण अनुप्रयोग से शुरू कर सकते हैं जो स्टेटलेस और स्टेटफुल विजेट का उपयोग करता है:
आधार आवेदनimport 'dart:math'; import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new MyHomePage(), ), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { Random rand = Random(); @override Widget build(BuildContext context) { return new ListView.builder(itemBuilder: (BuildContext context, int index) { return Text('Random number ${rand.nextInt(100)}',); }); } }
पूर्ण उदाहरण
यदि आपको और अधिक कठिन परिस्थितियों की आवश्यकता है
चलो आगे बढ़ते हैं। विजेट्स की स्टेटफुल स्थिति को रिड्रेसिंग इंटरफेस के बीच बनाए रखा जाता है, लेकिन केवल जब तक विजेट की आवश्यकता होती है, अर्थात्। वास्तव में स्क्रीन पर स्थित है। आइए एक सरल प्रयोग करें - हमारी सूची टैब पर रखें:
टैब ऐप class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { Random rand = Random(); TabController _tabController; final List<Tab> myTabs = <Tab>[ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() { super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [ new ListView.builder(itemBuilder: (BuildContext context, int index) { return Text('Random number ${rand.nextInt(100)}',); }), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); } }
पूर्ण उदाहरण
स्टार्टअप पर, आप देख सकते हैं कि टैब के बीच स्विच करने पर, राज्य को हटा दिया जाता है (डिस्पोज़ () विधि कहा जाता है), जब इसे वापस किया जाता है, तो इसे फिर से बनाया जाता है (initState () विधि)। यह उचित है, क्योंकि गैर-प्रदर्शन योग्य विजेट की स्थिति को संग्रहीत करने से सिस्टम संसाधनों का उपभोग होगा। उस स्थिति में जब विजेट की स्थिति पूरी तरह से छिपी होनी चाहिए, कई दृष्टिकोण संभव हैं:
सबसे पहले, आप राज्य को संग्रहीत करने के लिए अलग-अलग ऑब्जेक्ट (ViewModel) का उपयोग कर सकते हैं। भाषा के स्तर पर डार्ट कारखाने के निर्माणकर्ताओं का समर्थन करता है जिनका उपयोग कारखानों और एकल बनाने के लिए किया जा सकता है जो आवश्यक डेटा संग्रहीत करते हैं।
मुझे यह दृष्टिकोण अधिक पसंद है, क्योंकि यह आपको उपयोगकर्ता इंटरफ़ेस से व्यावसायिक तर्क को अलग करने की अनुमति देता है। यह इस तथ्य के कारण विशेष रूप से सच है कि फ़्लटर रिलीज़ प्रीव्यू 2 ने आईओएस के लिए पिक्सेल-सही इंटरफेस बनाने की क्षमता को जोड़ा, लेकिन आपको निश्चित रूप से इसी विजेट्स पर ऐसा करने की आवश्यकता है।
दूसरे, रिएक्ट प्रोग्रामर्स से परिचित होने पर, स्टेट-राइजिंग दृष्टिकोण का उपयोग करना संभव है, जब डेटा अपस्ट्रीम में स्थित घटकों में संग्रहीत होता है। चूंकि फ़्लटर केवल इंटरफ़ेस को फिर से परिभाषित करता है, जब सेटस्टेट () विधि कहा जाता है, इस डेटा को बिना रेंडर किए और बदला जा सकता है। यह दृष्टिकोण कुछ अधिक जटिल है और संरचना में विगेट्स की कनेक्टिविटी को बढ़ाता है, लेकिन आपको डेटा स्टोरेज पॉइंट के स्तर को निर्दिष्ट करने की अनुमति देता है।
अंत में, flutter_redux जैसे राज्य भंडारण पुस्तकालय हैं।
सादगी के लिए, हम पहले दृष्टिकोण का उपयोग करते हैं। चलो एक अलग लिस्टडाटा वर्ग बनाते हैं, सिंगलटन, जो हमारी सूची के लिए मूल्यों को संग्रहीत करता है। प्रदर्शित करते समय, हम इस वर्ग का उपयोग करेंगे।
टैब्ड डेटा रिकवरी एप्लिकेशन class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { TabController _tabController; final List<Tab> myTabs = <Tab>[ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() { super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [ new ListView.builder(itemBuilder: ListData().build), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); } } class ListData { static ListData _instance = ListData._internal(); ListData._internal(); factory ListData() { return _instance; } Random _rand = Random(); Map<int, int> _values = new Map(); Widget build (BuildContext context, int index) { if (!_values.containsKey(index)) { _values[index] = _rand.nextInt(100); } return Text('Random number ${_values[index]}',); } }
पूर्ण उदाहरण
एक स्क्रॉल स्थिति सहेजना
यदि आप पिछले उदाहरण से सूची को नीचे स्क्रॉल करते हैं, तो टैब के बीच स्विच करें, यह नोटिस करना आसान है कि स्क्रॉल स्थिति सहेजी नहीं गई है। यह तर्कसंगत है, क्योंकि यह हमारे लिस्टडाटा वर्ग में संग्रहीत नहीं है, और विजेट की अपनी स्थिति टैब के बीच स्विच करने से नहीं बचती है। हम स्क्रॉलिंग स्टेट स्टोरेज को मैन्युअल रूप से कार्यान्वित करते हैं, लेकिन मज़े के लिए हम इसे अलग वर्ग में नहीं जोड़ेंगे और लिस्टडेटा में नहीं, बल्कि उच्च स्तर की स्थिति में यह दिखाने के लिए कि इसके साथ कैसे काम करें
ScrollController और NotificationListener विगेट्स (साथ ही पहले इस्तेमाल किया DefaultTabController) पर ध्यान दें। विगेट्स की अवधारणा जिसमें उनका डिस्प्ले नहीं है, उन्हें रिएक्ट / रेडक्स के साथ काम करने वाले डेवलपर्स से परिचित होना चाहिए - कंटेनर घटक सक्रिय रूप से इस बंडल में उपयोग किए जाते हैं। स्पंदन में, गैर-डिस्प्ले विगेट्स आमतौर पर चाइल्ड विजेट्स में कार्यक्षमता जोड़ने के लिए उपयोग किए जाते हैं। इससे आप विजुअल विजेट्स को खुद ही हल्का छोड़ सकते हैं और सिस्टम ईवेंट को प्रोसेस नहीं कर सकते हैं जहाँ उनकी ज़रूरत नहीं है।
यह कोड Marcin Szałek द्वारा Stakoverflow ( https://stackoverflow.com/questions/45341721/flutter-listview-inside-on-a-tabbarview-loses-its-scroll-position ) पर प्रस्तावित समाधान पर आधारित है। योजना इस प्रकार है:
- स्क्रॉल स्थिति के साथ काम करने के लिए सूची में एक स्क्रॉलकंट्रोलर जोड़ें।
- स्क्रॉल स्थिति को पास करने के लिए सूची में NotificationListener जोड़ें।
- हम स्क्रॉल स्थिति को _MyHomePageState (जो टैब के ऊपर एक स्तर ऊपर है) में सहेजते हैं और इसे सूची स्क्रॉल के साथ जोड़ते हैं।
स्क्रॉल स्थिति को सहेजने के साथ आवेदन class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { double listViewOffset=0.0; TabController _tabController; final List<Tab> myTabs = <Tab>[ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() { super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [new ListTab( getOffsetMethod: () => listViewOffset, setOffsetMethod: (offset) => this.listViewOffset = offset, ), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); } } class ListTab extends StatefulWidget { ListTab({Key key, this.getOffsetMethod, this.setOffsetMethod}) : super(key: key); final GetOffsetMethod getOffsetMethod; final SetOffsetMethod setOffsetMethod; @override _ListTabState createState() => _ListTabState(); } class _ListTabState extends State<ListTab> { ScrollController scrollController; @override void initState() { super.initState(); //Init scrolling to preserve it scrollController = new ScrollController( initialScrollOffset: widget.getOffsetMethod() ); } @override Widget build(BuildContext context) { return NotificationListener( child: new ListView.builder( controller: scrollController, itemBuilder: ListData().build, ), onNotification: (notification) { if (notification is ScrollNotification) { widget.setOffsetMethod(notification.metrics.pixels); } }, ); } }
पूर्ण उदाहरण
आवेदन बंद होने का अनुभव
एप्लिकेशन की अवधि के लिए जानकारी सहेजना अच्छा है, लेकिन अक्सर आप इसे सत्रों के बीच सहेजना चाहते हैं, विशेष रूप से ऑपरेटिंग सिस्टम की आदत पर विचार करने के लिए जब पृष्ठभूमि में पर्याप्त मेमोरी नहीं होती है। स्पंदन में लगातार डेटा भंडारण के लिए मुख्य विकल्प हैं:
- साझा प्राथमिकताएं ( https://pub.dartlang.org/packages/sared_preferences ) NSUserDefaults (iOS पर) और SharedPreferences (Android पर) के आसपास एक आवरण है और आपको कुंजी-मूल्य वाले जोड़े की एक छोटी संख्या संग्रहीत करने की अनुमति देता है। सेटिंग्स संग्रहीत करने के लिए बढ़िया है।
- sqflite ( https://pub.dartlang.org/packages/sqflite ) SQLite के साथ काम करने के लिए एक प्लगइन है (कुछ सीमाओं के साथ)। निम्न स्तर के प्रश्नों और सहायकों दोनों का समर्थन करता है। इसके अलावा, कक्ष के साथ सादृश्य द्वारा, यह आपको डेटाबेस स्कीमा संस्करणों के साथ काम करने देता है और एप्लिकेशन को अपडेट करते समय स्कीमा को अपडेट करने के लिए कोड सेट करता है।
- क्लाउड फायरस्टोर ( https://pub.dartlang.org/packages/cloud_firestore ) आधिकारिक फायरबेस प्लगइन परिवार का हिस्सा है।
प्रदर्शित करने के लिए, हम स्क्रॉल राज्य को साझा प्राथमिकताओं में सहेजेंगे। ऐसा करने के लिए, _MyHomePageState स्थिति को प्रारंभ करते समय और स्क्रॉल करते समय सहेजते समय स्क्रॉल स्थिति की बहाली जोड़ें।
यहां हमें अतुल्यकालिक फ़्लटर / डार्ट मॉडल पर रहने की आवश्यकता है, क्योंकि सभी बाहरी सेवाएं अतुल्यकालिक कॉल पर काम करती हैं। इस मॉडल के संचालन का सिद्धांत नोड.जेएस के समान है - निष्पादन (थ्रेड) का एक मुख्य धागा है, जो एसिंक्रोनस कॉल द्वारा बाधित होता है। प्रत्येक बाद की रुकावट पर (और यूआई उन्हें लगातार बनाता है), पूर्ण किए गए अतुल्यकालिक संचालन के परिणाम संसाधित होते हैं। इसी समय, पृष्ठभूमि थ्रेड्स (गणना फ़ंक्शन के माध्यम से) में भारी गणनाओं को चलाना संभव है।
तो, SharedPreferences में लिखना और पढ़ना अतुल्यकालिक रूप से किया जाता है (हालांकि पुस्तकालय कैश से सिंक्रोनस रीडिंग की अनुमति देता है)। शुरुआत करने के लिए, हम पढ़ने से निपटेंगे। अतुल्यकालिक डेटा पुनर्प्राप्ति के लिए मानक दृष्टिकोण इस तरह दिखता है - अतुल्यकालिक प्रक्रिया शुरू करें, और इसके पूरा होने पर, सेटस्टैट को निष्पादित करें, प्राप्त मानों को लिखें। परिणामस्वरूप, प्राप्त डेटा का उपयोग करके उपयोगकर्ता इंटरफ़ेस अपडेट किया जाएगा। हालाँकि, इस मामले में, हम डेटा के साथ काम नहीं कर रहे हैं, लेकिन स्क्रॉल स्थिति के साथ। हमें इंटरफ़ेस को अपडेट करने की आवश्यकता नहीं है, हमें केवल स्क्रॉलकंट्रोलर पर जंप्टो विधि को कॉल करने की आवश्यकता है। समस्या यह है कि एक एसिंक्रोनस अनुरोध को संसाधित करने का परिणाम किसी भी समय वापस आ सकता है और यह जरूरी नहीं कि क्या और कहां स्क्रॉल करना है। पूरी तरह से आरंभिक इंटरफ़ेस पर एक ऑपरेशन करने की गारंटी देने के लिए, हमें ... अभी भी सेटस्टेट के अंदर स्क्रॉल करना होगा।
हमें इस कोड जैसा कुछ मिलता है:
राज्य की स्थापना @override void initState() { super.initState(); //Init scrolling to preserve it scrollController = new ScrollController( initialScrollOffset: widget.getOffsetMethod() ); _restoreState().then((double value) => scrollController.jumpTo(value)); } Future<double> _restoreState() async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getDouble('listViewOffset'); } void setScroll(double value) { setState(() { scrollController.jumpTo(value); }); }
रिकॉर्ड के साथ, सब कुछ अधिक दिलचस्प है। तथ्य यह है कि स्क्रॉल करने की प्रक्रिया में, इस बारे में रिपोर्टिंग की घटनाएं लगातार आती हैं। हर बार एसिंक्रोनस रिकॉर्डिंग शुरू करने से मूल्य बदल जाता है, जिससे एप्लिकेशन त्रुटियां हो सकती हैं। हमें श्रृंखला से केवल अंतिम घटना को संसाधित करने की आवश्यकता है। प्रतिक्रियाशील प्रोग्रामिंग के संदर्भ में, इसे बहस कहा जाता है और हम इसका उपयोग करेंगे। डार्ट डेटा स्ट्रीम के माध्यम से प्रतिक्रियाशील प्रोग्रामिंग की मुख्य विशेषताओं का समर्थन करता है, इसलिए हमें स्क्रॉल स्थिति अपडेट से एक स्ट्रीम बनाने और इसे सदस्यता लेने की आवश्यकता होगी, इसे डेब्यू का उपयोग करके परिवर्तित करना। परिवर्तित करने के लिए, हमें stream_transform लाइब्रेरी की आवश्यकता है । वैकल्पिक दृष्टिकोण के रूप में, आप RxDart का उपयोग कर सकते हैं और ReactiveX के संदर्भ में काम कर सकते हैं।
यह निम्नलिखित कोड का पता लगाता है:
स्थिति रिकॉर्ड StreamSubscription _stream; StreamController<double> _controller = new StreamController<double>.broadcast(); @override void initState() { super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); _stream = _controller.stream.transform(debounce(new Duration(milliseconds: 500))).listen(_saveState); } void _saveState(double value) async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setDouble('listViewOffset', value); }
पूर्ण उदाहरण