Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रणाली >> Android

Kotlin और Jetpack Compose के साथ एंड्रॉइड ऐप बनाएं – Sudoku ऐप बनाएं

Jetpack Compose Android का आधुनिक टूलकिट है native UI बनाने के लिए। यह Android पर UI डेवलपमेंट को सरल और तेज करता है।

हमने freeCodeCamp.org के YouTube चैनल पर एक कोर्स जारी किया है जो आपको सिखाएगा कि Kotlin और Jetpack Compose के साथ एक Sudoku Android ऐप कैसे बनाएं।

इस दौरान आप graph data structures और algorithms के बारे में भी सीखेंगे।

यह कोर्स Ryan M. Kay ने विकसित किया है। Ryan एक अत्यधिक अनुभवी डेवलपर और शिक्षक हैं।

इस कोर्स में शामिल सेक्शन:

  • ऐप डिज़ाइन दृष्टिकोण: थर्ड-पार्टी लाइब्रेरी मिनिमलिज़्म और MV-Whatever आर्किटेक्चर
  • डोमेन पैकेज: रिपॉजिटरी पैटर्न, Enum, Data Class, Sealed Class, Hash Code, Interfaces
  • कॉमन पैकेज: एक्सटेंशन फंक्शन्स और वेरिएबल्स, ओपन-क्लोज्ड प्रिंसिपल (OCP), Abstract Class, Singleton
  • पर्सिस्टेंस (स्टोरेज) पैकेज: क्लीन आर्किटेक्चर बैक एंड Java फाइल सिस्टम स्टोरेज के साथ, Jetpack Proto DataStore
  • UI पैकेज: Jetpack Compose UI बेसिक्स, स्टाइल्स, टाइपोग्राफी, लाइट और डार्क थीम्स
  • UI कंपोनेंट्स पैकेज: मॉडिफायर्स, रीयूज़ेबल टूलबार और लोडिंग स्क्रीन्स
  • UI एक्टिव गेम फीचर पैकेज: प्रेजेंटेशन लॉजिक और ViewModel कोरूटीन के साथ, Kotlin फंक्शन टाइप्स
  • UI एक्टिव गेम फीचर पैकेज: Jetpack Compose UI और एक्टिविटी कंटेनर के साथ Sudoku गेम
  • कंप्यूटेशन लॉजिक पैकेज: n-sized स्क्वायर Sudokus के लिए Graph DS और Algos का ओवरव्यू, डिज़ाइन और टेस्टिंग

पूरा कोर्स नीचे देखें या freeCodeCamp.org YouTube चैनल पर (3.5 घंटे का वॉच)।

ट्रांसक्रिप्ट

(ऑटोजेनरेटेड)

इस कोर्स में, आप Jetpack Compose UI लाइब्रेरी का उपयोग करके एक Android ऐप बनाना सीखेंगे।

इस दौरान, आप graph algorithms और data structures के बारे में सीखेंगे।

Ryan M. Kay यह कोर्स पढ़ाते हैं।

वे एक बहुत अनुभवी डेवलपर और इंस्ट्रक्टर हैं।

क्या हाल है सब लोग? मैं Ryan हूं, और मैं आपको अपने ग्राफ Sudoku एप्लिकेशन पर ट्यूटोरियल सीरीज में स्वागत करना चाहता हूं।

यह एक एप्लिकेशन है जिसे मैंने मुख्य रूप से ग्राफ डेटा स्ट्रक्चर और एल्गोरिदम की अपनी समझ को refine करने के लिए लिखा था, और Android पर नई UI लाइब्रेरी Jetpack Compose के लिए।

सीरीज के इस भाग में, मैं सीरीज के मुख्य लक्ष्यों और विषयों का overview दूंगा और कुछ डिज़ाइन निर्णयों पर चर्चा करूंगा जो आपको आश्चर्यचकित कर सकते हैं।

मैं प्रोजेक्ट के पब्लिक वर्जन के सोर्स कोड को अप टू डेट रखने की कोशिश करूंगा।

और यह आपकी जिम्मेदारी होगी कि अगर कुछ deprecated हो जाता है या काम करना बंद कर देता है तो उस सोर्स कोड को देखें।

इस सीरीज का फॉर्मेट code-along स्टाइल है, जिसका तात्पर्य है कि सीखने का सबसे अच्छा तरीका है मेरे साथ कोड लिखना जैसे मैं इसे समझाता हूं।

एडवांस्ड डेवलपर्स के लिए, फुल सोर्स डायरेक्ट लर्निंग के लिए उपलब्ध है, लेकिन आप वीडियो देख सकते हैं अपने नॉलेज में gaps को clear करने के लिए।

मैं Kotlin भाषा के कुछ fairly advanced features और सॉफ्टवेयर आर्किटेक्चर के timeless principles का उपयोग करता हूं, तो आप एक या दो चीजें सीख सकते हैं।

बिगिनर्स के लिए, यह बहुत महत्वपूर्ण है कि आप कोड में मेरे साथ फॉलो करें लेकिन अपनी गति से।

आपको पहले ऐसा महसूस नहीं हो सकता कि आप प्रगति कर रहे हैं, लेकिन समझें कि आप कोड लिखने के कौशल का अभ्यास कर रहे होंगे भले ही आप अभी तक यह न समझें कि आप क्या लिख रहे हैं।

मैं गुप्त रूप से आपको यह भी सिखाऊंगा कि कैसे ऐसा कोड लिखें जो लिखने, पढ़ने, ठीक करने, सुधारने और टेस्ट करने में आसान हो।

लेकिन टाइमलेस सॉफ्टवेयर डिज़ाइन के सिद्धांतों को याद रखने के लिए कहने के बजाय, आप उन्हें प्रैक्टिस में सीखेंगे और उपयोग करेंगे जैसे हम इस एप्लिकेशन को बनाते हैं।

चार सामान्य विषय हैं जो यह ट्यूटोरियल demonstrate करेगा – ग्राफ डेटा स्ट्रक्चर और एल्गोरिदम, Jetpack Compose, क्लीन UI आर्किटेक्चर, और Kotlin भाषा फीचर्स।

हम डायरेक्टेड कलर्ड ग्राफ्स के विषय का पता लगाएंगे और n-sized Sudoku puzzles को generate, solve, और unsolve करने के लिए एल्गोरिदम लिखने में अपने experiments के बारे में।

हम Jetpack Compose का उपयोग करके पूरे यूजर इंटरफेस को बनाएंगे, जो हमें अपना UI पूरी तरह से Kotlin में बनाने की अनुमति देता है।

XML views और styles के विपरीत।

मैं आपको दिखाऊंगा कि SOLID और क्लीन आर्किटेक्चर में commonly expounded principles का उपयोग करके एक सिंपल ऐप को कैसे वायर अप करें।

हालांकि, मैं इन विषयों को अपने तरीके से पढ़ाता हूं, तो jargon या over-engineering के bunch की उम्मीद न करें।

मैं इस खूबसूरत प्रोग्रामिंग भाषा के basic और advanced features का कैसे और कब उपयोग करें, यह भी demonstrate करूंगा।

वीडियो का यह भाग intermediate और advanced डेवलपर्स के लिए अधिक intended है।

इस डिज़ाइन निर्णय को समझना ट्यूटोरियल पूरा करने के लिए आवश्यक नहीं है।

इस ट्यूटोरियल के throughout, आप notice करेंगे कि Compose और Proto DataStore के अलावा, मैं Android Jetpack से लगभग कोई लाइब्रेरी उपयोग नहीं करता। वास्तव में, बहुत कम थर्ड-पार्टी लाइब्रेरीज। Kotlin और Java स्टैंडर्ड लाइब्रेरीज और Android SDK पर भरोसा करके, मेरा कोड deprecations और लाइब्रेरीज में बदलाव के प्रति अधिक resistant हो जाता है।

ऐसा इसलिए क्योंकि Android SDK और स्टैंडर्ड लाइब्रेरीज थर्ड-पार्टी लाइब्रेरीज की तुलना में कम frequently बदलते हैं, जैसे कि आप Android Jetpack में देखते हैं।

इसका यह भी मतलब है कि कुछ चीजें जिन्हें Jetpack ViewModel, Jetpack Navigation, या Hilt जैसी लाइब्रेरीज handle कर सकती हैं, उन्हें हमें हाथ से लिखना होगा।

मुझे वास्तव में यह पसंद है, लेकिन आपका एक अलग value system हो सकता है।

और मेरा लक्ष्य यहां आपको इन टूल्स सीखने से discourage करना नहीं है अगर आप उनमें interested हैं।

इसके साथ ही कहा गया है, आप आश्चर्यचकित हो सकते हैं कि Jetpack ViewModel, Navigation, या Dependency Injection कोड को उनके बिना एक छोटे ऐप में लिखना कितना आसान है।

यह एप्लिकेशन Model-View-Whatever आर्किटेक्चर का उपयोग करता है, जो मेरा तरीका है यह कहने का कि मैं किसी और की स्टाइल फॉलो नहीं करता।

कई वर्षों तक इस विषय का अध्ययन करने के बाद, मैं प्रोजेक्ट की requirements और अच्छे सॉफ्टवेयर डिज़ाइन के सिद्धांतों की अपनी समझ को अपनी आर्किटेक्चर का guide बनने देता हूं।

इस मामले में, मुझे लगता है कि Compose MVVM-based दृष्टिकोण के लिए बना है, लेकिन मैंने एक प्रेजेंटेशन लॉजिक क्लास एक specific कारण से add की।

इस कारण को passive view या humble object कहा जाता है।

View या ViewModel को प्रत्येक स्क्रीन के control flow को manage करने देने के बजाय, मैंने उस लॉजिक को एक अलग क्लास में खींच लिया।

यह क्लास लिखने और टेस्ट करने में super easy है क्योंकि इसकी कोई थर्ड-पार्टी डिपेंडेंसी नहीं है।

और यह मेरे ViewModel को एक ugly God object बनने से रोकता है।

आप इसे कभी आजमाएं।

मैंने इस आर्किटेक्चर को simply सॉफ्टवेयर आर्किटेक्चर के single most important principle को apply करके डिज़ाइन किया है – separation of concerns।

इस सीरीज के इस भाग के लिए बस इतना ही।

अब हम कोडिंग शुरू करते हैं।


डोमेन पैकेज दो चीजों को represent करता है – सबसे general pieces of information, जैसे data classes, constants, और enums जिन्हें प्रोग्राम को virtually represent करना होगा।

साथ ही, सबसे general चीजें जो यह प्रोग्राम करना होगा, जिन्हें प्रोग्राम functions और interfaces का उपयोग करके represent करता है।

संक्षेप में, यह किसी भी नए प्रोग्राम का foundation है जिसे मैं लिखता हूं और मैं अपने डोमेन पैकेज या मॉड्यूल को डिज़ाइन करने के लिए एक repeatable process का उपयोग करता हूं।

उस process के clear और simple introduction के लिए।

मेरे चैनल पर इस वीडियो को देखें – How to Design Information Systems and Applications।

वह वीडियो एक talk की रिकॉर्डिंग है जो मैंने Egypt के कुछ सॉफ्टवेयर इंजीनियर्स को उस particular topic पर दी थी।

वैसे भी, इस पैकेज में अधिकांश कोड simple है, लेकिन इसमें एक डिज़ाइन पैटर्न शामिल है जिसे मैं अब introduce करूंगा।

इस पैकेज में several interfaces हैं, जिनका उपयोग रिपॉजिटरी डिज़ाइन पैटर्न को employ करने के लिए किया जाता है।

इस पैटर्न को facade pattern के रूप में भी जाना जाता है।

और पैटर्न का general goal इसकी technical definition से simpler है।

Facade या रिपॉजिटरी पैटर्न की technical definition है एक subsystem के details को hide करना, इस मामले में, data storage mechanisms को, एक abstraction के पीछे, इस मामले में, एक interface के पीछे।

चलिए एक practical example देखते हैं।

उस definition को sense बनाने के लिए, हमारे प्रेजेंटेशन लॉजिक क्लासेस इन रिपॉजिटरी इंटरफेसेज के references hold करेंगे, उन क्लासेस के बजाय जो इंटरफेसेज को implement करती हैं।

यह हमारे प्रेजेंटेशन लॉजिक क्लासेस को several benefits देता है।

उन्हें एक-दूसरे से independently built किया जा सकता है बिना compiler error cause किए, उन्हें fake implementation के साथ टेस्ट किया जा सकता है इंटरफेस के बिना हमें प्रेजेंटेशन लॉजिक क्लास के अंदर कोई कोड बदलने की आवश्यकता के बिना।

साथ ही, अगर हम इंटरफेस के एक different implementation का उपयोग करने का निर्णय लेते हैं, जैसे file storage से Room database पर switch करना, हम वह भी बिना प्रेजेंटेशन लॉजिक क्लास में किसी बदलाव की आवश्यकता के कर सकते हैं।

ये benefits loosely coupled software systems बनाने का परिणाम हैं।

और रिपॉजिटरी या facade pattern loosely coupled को promote करने का एक आसान तरीका है।

वास्तव में, interfaces in general loosely coupled को promote करते हैं।

अब हर जगह इस पैटर्न का उपयोग करने की आवश्यकता महसूस न करें।

A good general rule है उन्हें significant architectural boundaries में उपयोग करें।

इस मामले में, मैं उन्हें इस एप्लिकेशन के फ्रंट एंड और बैक एंड के बीच boundary के रूप में उपयोग कर रहा हूं।

चीजों को शुरू करने के लिए, डोमेन पैकेज पर right-click करें, New Kotlin File पर जाएं और Difficulty नाम की एक फाइल बनाएं।

और वह एक enum class होने जा रही है।

Kotlin, Java और विभिन्न अन्य भाषाओं में enum classes restricted set of values बनाने के लिए useful हैं।

जैसा कि हम बाद में देखेंगे, आप Kotlin में sealed classes का उपयोग restricted set of types बनाने के लिए कर सकते हैं।

किसी भी मामले में, restricted values का set बनाने के बाहर मुख्य benefit यह है कि enums आपके प्रोग्राम की legibility को greatly improve कर सकते हैं।

जैसा कि हम एक moment में देखेंगे, चलिए हमारे enum entries add करते हैं।

quite obviously, यह enum दिए गए Sudoku puzzle की difficulty को represent करेगा।

हालांकि, हमें आगे बढ़ने से पहले एक और चीज add करने की जरूरत है – हम actually कुछ values का उपयोग करने जा रहे हैं प्रत्येक entry के लिए algorithms में basically Sudoku puzzle की difficulty dictate करने के लिए।

तो एक enum में value add करने के लिए Kotlin में, हमें इसे एक property या कुछ properties देनी होगी।

जैसा कि आप देख सकते हैं, हमारे entries अब red हैं, obviously हमें उन्हें कुछ doubles देने की जरूरत है।

और वह सब कुछ है जो हमें यहां करने की जरूरत है।

Main पैकेज पर right-click करें, New Kotlin File या Class पर जाएं।

और हम Settings नाम का एक data class create करने जा रहे हैं।

Settings हमारा पहला data model है, जैसा मैं इसे call करना पसंद करता हूं या plain old Kotlin object।

और जैसा कि हम एक moment में देखेंगे, यह difficulty और boundary दोनों को contain करेगा, जो Sudoku puzzle का size है।

तो एक 4x4 Sudoku puzzle का boundary 4 होगा, एक 9x9 का boundary 9 होगा।

Data keyword जब class keyword से आगे रखा जाता है तो basically कुछ helper methods add या generate करता है, जैसे equals, hashCode, या copy।

हम definitely बाद में copy का उपयोग करेंगे maybe इस क्लास में नहीं, लेकिन कुछ में और दूसरों में।

किसी point पर, हम generated hashCode function का भी उपयोग करेंगे।

वैसे भी, यह एक really simple क्लास है, हम बस इसमें दो properties add करने जा रहे हैं।

और वह है इसके लिए।

डोमेन पैकेज पर right-click करें, New Kotlin Class या File पर जाएं।

और इस बार, हम UserStatistics नाम का एक क्लास create करने जा रहे हैं।

और वह एक data class होने जा रहा है।

अब, इस क्लास का purpose user के shortest/best times को represent करना है किसी particular difficulty या size के Sudoku puzzle को solve करने के लिए।

तो हम basically just properties का एक पूरा bunch add करने जा रहे हैं जो pretty much same हैं।

अब, एक चीज जो आप IntelliJ IDEA या Android Studio में कर सकते हैं वह है आप वहां क्लिक कर सकते हैं और फिर Ctrl+D hit कर सकते हैं, जितनी बार आपको जरूरत हो और वह एक नई लाइन पर copy कर देगा।

वह basically इस क्लास के लिए है।

अब, आप wonder कर रहे होंगे कि हम Long यहां क्यों उपयोग कर रहे हैं – हम actually user को एक particular game को complete करने में लगने वाले समय को milliseconds में store कर रहे हैं।

इसीलिए हम long integer value चाहते हैं।

फिर से, डोमेन पैकेज पर right-click करें, New Kotlin File या Class पर जाएं, और यह SudokuNode नाम का एक data class होने जा रहा है।

अब, चीजें हमारे data models के साथ थोड़ी अधिक complicated और interesting होने लगती हैं।

तो मैं यहां क्या कर रहा हूं वह है एक graph data structure में individual node को represent करना। हम बाद में graph data structures के बारे में बहुत अधिक बात करेंगे जब हम ट्यूटोरियल के उस भाग पर पहुंचेंगे जो इसके लिए dedicated है।

लेकिन just आपको एक idea देने के लिए – हम actually एक directed colored graph बनाने जा रहे हैं।

और यहां note करने के लिए एक important चीज है कि इस particular case में, यह term color really just एक number को refer करता है, यह सिर्फ एक value है जिसे हम किसी तरह के node में associate करते हैं।

और फिर से, आपको आगे का preview देने के लिए – जब मैं graph data structure कहता हूं, तो इसके लिए एक बेहतर नाम network data structure होगा, क्योंकि यह essentially वही है।

यह nodes का एक collection है, और edges, जो essentially nodes के बीच lines की तरह हैं – nodes के बीच relationships।

वैसे भी, हमारे data structure में यह particular node structure एक color या value रखने वाला है, जो just एक integer है one through nine या one through four, 0 भी शामिल है – 0 एक empty Sudoku tile को represent करता है, लेकिन वह front end की अधिक चिंता है।

ये nodes X और Y coordinates भी include करेंगे – top-left होगा x=0, y=0, bottom-right होगा x=8, y=8, और हम zero-based indexing का उपयोग करने जा रहे हैं।

तो x=1 से x=9 शुरू करने के बजाय, हम basically उससे 1 subtract कर देते हैं।

तो वह है उस preamble के लिए, चलिए कोड लिखना शुरू करते हैं।

तो हम X और Y values से शुरू करेंगे।

Next, हम color add करेंगे जो एक variable होगा क्योंकि यह execution के दौरान बदल सकता है।

फिर हम एक Boolean add करेंगे जिसे readOnly कहेंगे और मैं समझाऊंगा कि वह क्या है इसे लिखने के बाद।

अब, readOnly Boolean का purpose यहां quite simple है।

जब हम essentially एक Sudoku puzzle generate और फिर unsolve करते हैं, जो एक नया Sudoku puzzle create करने और फिर game को actually playable और fun बनाने के लिए clues की एक certain number remove करने का दूसरा तरीका है।

Sudoku board पर या Sudoku graph data structure में उन नंबर्स में से कुछ read-only होंगे – वे given clues की तरह हैं जिन्हें user खुद change करने के लिए supposed नहीं है।

जैसा कि हम बाद में देखेंगे।

यह user interface को भी affect करेगा क्योंकि हम read-only Sudoku nodes या tiles को उनसे differently draw करेंगे जिन्हें user edit कर सकता है।

अब, हम अभी तक done नहीं हैं, हमें hashCode function को override करना होगा जो data class keyword द्वारा provided था जैसा मैंने पहले discuss किया था, और फिर हमें एक function भी add करना होगा hash code पाने के लिए।

Okay, तो hashCode का एक default implementation होगा जो यहां ऊपर अलग-अलग properties में provided values पर based होगा, हम actually कुछ different करने जा रहे हैं।

तो हम getHash type करेंगे, जैसे कि।

And यह actually एक function है जिसे हम create करने जा रहे हैं also, हम यहां X और Y के लिए दो parameters add करेंगे।

Okay, हम अब getHash function add करने जा रहे हैं, और यह top-level होने जा रहा है, जिसका मतलब है यह हमारे SudokuNode क्लास के brackets के बाहर बैठता है।

Okay, चलिए just इस function को implement करते हैं, और फिर मैं समझाऊंगा कि हम यहां क्या कर रहे हैं।

Okay, तो मुझे शुरू करने दें यह explain करके कि exactly एक hash code या hash value क्या है।

तो यह essentially किसी तरह का generated key या unique identifier है किसी तरह के algorithm पर based।

इस मामले में, मेरे पास एक बहुत simple algorithm है – मैं बस X value को 100 से multiply करता हूं।

और मैं Y value को अकेला छोड़ देता हूं।

And मैं basically उन दो values को एक integer में combine कर देता हूं।

अब, reason क्यों मैं X को 100 से multiply करता हूं, वह है अगर मैं ऐसा न करूं तो 9x9 Sudoku puzzle में certain edge cases होंगे जहां भले ही X और Y value technically different हों, resulting hash code several different nodes के लिए unique नहीं होगा।

Simply put, मैं 100 से multiply कर रहा हूं ताकि Sudoku puzzle में हर individual tile के लिए hash codes unique हों।

जहां तक बात है कि हम generally hashCode का उपयोग क्यों कर रहे हैं, मैं इसे fairly straightforward रखने की कोशिश करूंगा।

Basically, हम graph में प्रत्येक node को एक LinkedHashMap में store करने जा रहे हैं।

तो hash values उस hash map के लिए key represent करेंगे।

एक HashMap में key-value pairs होते हैं, अगर आप aware नहीं हैं तो, हम उसे एक moment में देखेंगे।

लेकिन यह really useful निकलता है क्योंकि हमारा यूजर इंटरफेस भी चीजों को उसी तरह के X और Y coordinate style में represent करेगा।

तो बस मेरी बात मान लें।

Hash codes उन situations में pretty handy होते हैं जहां आपके पास large number of elements होते हैं, और आप necessarily हर individual element के लिए reference maintain नहीं करना चाहते।

इसके बजाय, हम just hash code प्राप्त करके एक reference retrieve कर सकते हैं।

Oh, और इससे पहले कि हम जाएं, हमें यहां एक और चीज add करने की जरूरत है – हम इसे Serializable implement करने जा रहे हैं।

अनिवार्य रूप से, यह क्या करता है यह हमें allow करता है हमारे Sudoku nodes और साथ ही whole puzzle को एक फाइल में read और write करने के लिए।

चूंकि हम एक समय में केवल एक puzzle store करने जा रहे हैं, मैं really database जैसी किसी चीज का उपयोग नहीं करना चाहता था, यह kind of just more sense बना रहा था file system का उपयोग करने के लिए, और Serializable इसे essentially आसान बनाता है।

Okay, हमारे पास इस particular पैकेज में create करने के लिए एक final data model है। फिर से right-click करें, New Kotlin File या Class, यह वाला SudokuPuzzle कहलाएगा।

और फिर से, यह एक data class होने जा रहा है।

Data models के बारे में सोचने का एक good way, जैसा मैं उन्हें call करना पसंद करता हूं वह यह है कि वे एक real-world object के virtual representations हैं, इस मामले में, एक Sudoku puzzle।

जिस तरह से मैंने initially इस क्लास को design किया वह critical questions पूछकर था कि क्या constitute करता है एक Sudoku puzzle।

चीजें जैसे boundaries – तो क्या चार tiles per row या column हैं या नौ tiles हैं उदाहरण के लिए, हमारे पास difficulty है।

और most importantly, हमारे पास graph data structure itself है।

वहां elapsed time भी है जो user ने एक particular puzzle को solve करने में लिया है।

तो चलिए उन properties को add करते हैं और फिर मैं उनमें से कुछ को explain करूंगा जिन्हें explain करने की जरूरत है।

Also, इससे पहले कि मैं भूल जाऊं, चलिए Serializable implementation यहां add करते हैं।

Okay, तो आप probably wonder कर रहे हैं कि buildNewSudoku क्या है।

तो हम क्या करने जा रहे हैं वह है हम actually एक large series of top-level functions call करने जा रहे हैं, जो हमारे सभी different algorithms form करेंगे, जो necessary हैं एक नया Sudoku puzzle build और generate करने के लिए, एक given size के based पर, जो boundary represent करता है और एक given difficulty।

Also, अगर आप wonder कर रहे हैं कि LinkedHashMap के साथ क्या हो रहा है जो LinkedLists से भरा है, वह kind of एक तरीका है adjacency list को represent करने का।

फिर से, मैं उन details में जाऊंगा graph data structures और उन्हें represent करने के different ways पर, या कम से कम मैं उन्हें इस particular application में कैसे represent करता हूं, बाद में जब हम उस particular package और topic पर पहुंचेंगे।

लेकिन फिलहाल समझें कि यह हमारे virtual Sudoku puzzle को represent करता है।

आखिरी चीज जो हम करने जा रहे हैं वह है just एक छोटा सा method add करना, जो just इसे थोड़ा more obvious और legible बनाता है कि graph itself को कैसे get करना है, कम से कम मेरी opinion में।

And हम single-expression syntax का उपयोग करने जा रहे हैं।

तो मैं just equals graph type करूंगा।

चलिए IGameRepository नाम का एक नया interface create करते हैं।

मुझे interfaces के सामने capital I लगाने की naming convention पसंद है।

और फिर जैसा हम बाद में persistence package में देखेंगे, मैं implementation करने वाली classes के लिए naturally IMPLL suffix add करूंगा।

एक small to medium scale application डिज़ाइन करते समय, एक चीज जो आप कर सकते हैं वह यह है कि आप actually conceptually अपने रिपॉजिटरी इंटरफेसेज में functions को think कर सकते हैं।

As use cases themselves – जो चीजें हम यहां लिखने जा रहे हैं वे saveGame, updateGame, updateNode जैसी चीजें कहलाएंगी, जो बहुत similar है अगर आप user stories या use cases टाइप application design कर रहे हैं।

तो मैं बस इसे वहां throw करना चाहता था।

एक more complicated application में, जहां मुझे multiple different data sources और repositories के coordination की अधिक आवश्यकता होती, मैं probably interactor या use case रखता, जैसा कि commonly बात की जाती है, Martin Fowler या Robert Martin, Uncle Bob के बीच।

लेकिन इस particular मामले में, simpler applications में, generally speaking, use case as a class itself, typically एक unnecessary extra layer of abstraction है।

तो यहां हम just अपने presenter, या ViewModel, या whatever के साथ जा रहे हैं, directly एक रिपॉजिटरी से बात कर रहे हैं।

And वह इस आकार के एप्लिकेशन के लिए sufficient amount of abstraction है।

अब, एक interface कैसे काम करता है – यह एक class के बहुत similar है except जैसा हम एक moment में देखेंगे, हम actually functions को implement या body देने के लिए allowed नहीं हैं जो हम लिखने जा रहे हैं, हम just लिखते हैं जिसे commonly abstract functions या function stubs कहा जाता है।

अब यहां दो important points हैं।

पहला, suspend keyword वहां applied है, क्योंकि ये functions actually co-routine scopes से call किए जाएंगे, जो logic class या presenter में exist करते हैं जो इस particular interface को reference करेगा।

तो वह सब है जो हमें अभी concurrency establish करने के लिए करने की जरूरत है।

अब, case में आप familiar नहीं हैं, जो मैं यहां उपयोग कर रहा हूं वह है जिसे function type के रूप में जाना जाता है।

और तो हम क्या करेंगे वह यह है कि हम actually logic class में exist करने वाले दो functions के references को pass in करेंगे – प्रेजेंटेशन लॉजिक उन functions को pass in करेगा।

और फिर रिपॉजिटरी implementations में – जो चीजें इस particular interface को implement करती हैं, वह है कि वे कैसे call back करेंगे किसी तरह के result के साथ, या तो successful result या failure।

अब आप wonder कर रहे होंगे कि Unit और फिर यह Unit return क्यों करता है जो वह arrow means है, इस particular case में? यह कुछ ऐसा है जो हमें करना kind of have to do है ताकि Kotlin compiler exactly समझ सके कि हम उसे क्या करने के लिए कह रहे हैं।

तो यह kind of equivalent होगा void या basically nothing को इस particular function में pass करने के, और फिर उस particular function से nothing return करने के।

लेकिन जो यह करेगा वह है signal कुछ ऐसा जैसे onComplete अगर आपके पास RxJava के साथ कोई experience है, जहां basically हम just चाहते हैं कि application successfully resume जब यह function call हो।

लेकिन यह particular function actually कुछ भी return करने की आवश्यकता नहीं है।

बाद में, हम कुछ examples देखेंगे जब हम actually अपने onSuccess function type के through एक value return करने की आवश्यकता होती है।

फिर से, आप wonder कर रहे होंगे कि game save करने और game update करने में क्या difference है? Well, essentially, updateGame function में, हम जा रहे हैं wholesale entire Sudoku puzzle write करने के लिए, जिसमें elapsed time शामिल है, हालांकि कुछ situations हैं जहां हम वास्तव में सिर्फ game के elapsed time को update करना चाहते हैं, जैसे जब user application से navigate away करता है।

तो जो मैं यहां कर रहा हूं वह है specialized functions create करना depending on क्या हम user के point of view से achieve करना चाहते हैं।

अब मैं demonstrate करूंगा जब हम actually अपने onSuccess function types से एक value return करना चाहते हैं।

Okay, तो onSuccess के लिए, जो हम effectively कह रहे हैं वह यह है कि यह particular function must be called किसी तरह के Boolean value के साथ, obviously either true या false जब इसे इस particular suspending function के implementation में call किया जाता है।

अब, basically जो हम यहां कर रहे हैं वह यह है कि जब user एक single node या tile को Sudoku puzzle में update करता है, तो एक possibility है कि वह puzzle में final tile हो।

And उस particular case में, assuming puzzle correct है, और user ने final tile input किया है, वह signal करेगा कि game complete है।

तो यह यहां थोड़ा confusing हो सकता है।

लेकिन onSuccess necessarily यह नहीं mean करता कि game itself complete हो गया है।

इसीलिए मैं वहां differentiate कर रहा हूं।

Okay, अब इस particular case में, जो हम कर रहे हैं वह यह है कि जब user active game पर वापस आता है, तो हम obviously current game प्राप्त करना चाहते हैं।

And वहां actually एक edge case है जहां user game complete करता है, application से navigate away करता है, और फिर application restart करता है।

And so इसीलिए हम अभी भी उस isComplete flag को pass in कर रहे हैं।

यहां हम obviously एक Settings object return करने जा रहे हैं।

चलिए एक और interface create करते हैं।

और यह वाला IGameDataStorage कहलाएगा।

And obviously, यह एक interface होने जा रहा है।

अब, इससे पहले कि हम interface itself लिखें, हम कुछ slightly different करने जा रहे हैं।

हम एक result wrapper create करने जा रहे हैं या Well, let's just say यह either monad से inspired है functional programmers से, लेकिन कोई really care नहीं करता कि functional programmers क्या सोचते या बात करते हैं।

Sealed classes truly Kotlin प्रोग्रामिंग भाषा की मेरी favorite simple features में से एक हैं।

यह हमें एक restricted set of types create करने की अनुमति देता है, और उन types में particular values हो सकती हैं।

And basically जो यह करेगा और जैसा हम एक moment में देखेंगे वह यह है कि यह हमें एक object को एक particular function of IGameDataStorage से return करने की अनुमति देता है।

And यह particular object multiple different states को represent करने में capable है।

तो जैसे मैंने पहले IGameRepository में दिखाया, यहां हम error state और success state को दो separate function references के रूप में represent कर रहे हैं।

इस example में, हम दोनों उन states को एक single object के through represent करने जा रहे हैं।

अब, एक final point, इससे पहले कि हम आगे बढ़ें – अगर आपके पास, उदाहरण के लिए, कुछ तरह की situation है जहां आप just एक success case represent करना चाहते हैं, तो equivalent to just returning Unit in onSuccess, आप actually just एक object उपयोग कर सकते हैं – तो आप कह सकते हैं जैसे object OnComplete, etc।

लेकिन हम actually वह इस application में नहीं करते।

तो आपको उसे add करने की जरूरत नहीं है।

अब, चलिए interface को finish off करते हैं।

ISettingsStorage नाम का एक और interface create करें।

अब, हम यहां भी result wrappers उपयोग करने जा रहे हैं।

तो मैं actually बस इसे copy और paste करने जा रहा हूं।

And हम just कुछ चीजें rename करने जा रहे हैं।

Okay, और अब हम interface लिख सकते हैं।

इस पैकेज के लिए एक और interface जाना है।

यह वाला IStatisticsRepository कहलाएगा, अगर मैं spell कर सकूं, और obviously, यह एक interface होने जा रहा है।

अब एक final point, फिर से – record actually user interface में कुछ affect करने वाला है based on whether या नहीं एक statistic जो update होता है वह एक record है यानी किसी particular boundary या size के Sudoku puzzle के लिए, और एक particular difficulty के लिए shortest possible time of completion।

कॉमन पैकेज में वह कोड होता है जिसे variety of different classes और functions में reused किया जाता है।

ट्यूटोरियल के इस भाग में, हम Kotlin भाषा के कई अलग-अलग features के बारे में सीखेंगे, जो कोड को intelligent और efficient तरीके से share करने के लिए designed हैं।

कवर किए गए topics में शामिल हैं – एक्सटेंशन फंक्शन्स और प्रॉपर्टीज, abstract classes, ओपन-क्लोज्ड प्रिंसिपल, object, Singleton, और कोरूटीन डिस्पैचर्स।

कोड लिखने से पहले, चलिए Open-Closed Principle के बारे में बात करते हैं।

OCP एक relatively confusing concept है, लेकिन मैं इसे clearest way में explain करने की कोशिश करूंगा जो मैं कर सकता हूं – मेरी own verbal definition देने के लिए, जिसे हम बाद में break down करेंगे।

कोई भी commonly reused software entity, जिसके change होने की expected है, उसका एक fixed public interface होना चाहिए, और उसके implementation को change करने का एक तरीका होना चाहिए।

उस particular definition को sense बनाने के लिए, कुछ चीजें हैं जिन्हें मुझे unpack करने की जरूरत है।

पहला, जब मैं software entity कहता हूं, तो मैं generally एक class या function के बारे में बोल रहा हूं, लेकिन यह कुछ अन्य चीजें भी हो सकती हैं।

तो चलिए examine करते हैं कि मेरा public interface से क्या मतलब है और क्यों मैं argue करता हूं कि यह fixed होना चाहिए अगर यह commonly reused है।

Public interface से, मैं Java या Kotlin interface specifically के बारे में नहीं बोल रहा, इसके बजाय, मेरा मतलब है किसी class या function के किसी भी publicly visible aspect से।

चूंकि यह एक Android tutorial है, आइए Activity class का example लेते हैं।

एक Activity मेरी requirement को fit करती है commonly reused होने की और expected to change over time।

तो यह OCP के बारे में सोचने के लिए एक perfect case है।

Activity के हर subclass को onCreate function include करना होगा, जो public interface का एक part है जिसे यह classes को available कराता है जो इसे reference करती हैं।

Reason क्यों हम इस public interface को change नहीं करना चाहते वह बहुत simple है।

Suppose कि Android platform developers अचानक decide कर लें कि saved instance state bundles को सभी lifecycle functions से deprecate और remove कर दें।

क्योंकि यह public interface pretty much हर Android program द्वारा use किया जाता है around, यह platform update everyone का code break कर देगा।

मेरा specifically मतलब है कि सभी Activity subclasses सभी codebases में जिन्होंने इस parameter को नहीं हटाया है, compile नहीं कर पाएंगे।

इसीलिए मैं specifically commonly reused software entities जैसे Activity के बारे में बात कर रहा हूं, और क्यों यह important है कि उनके public interfaces जितना संभव हो उतना कम change करें।

चूंकि हमने establish कर लिया है कि fixed public interfaces क्यों really important हैं।

Next question simple है।

हम फिर public interface के implementation को change करने के लिए एक mechanism या तरीका कैसे provide करते हैं? Well, यह पता चलता है कि Kotlin आपको इस problem को solve करने के लिए many options देता है।

Rather than उन सभी को verbally explain करने के, मैं आपको सिखाऊंगा कि कोड में उनका उपयोग कैसे करें।

जैसे हम इस एप्लिकेशन को बनाते हैं, कॉमन पैकेज पर right-click करें, और New Kotlin File या Class पर जाएं।

And यह actually एक abstract class होने जा रहा है, जो वे यहां option नहीं देते।

तो हम क्या करने जा रहे हैं वह है BaseLogic type करना, हम abstract keyword add करेंगे।

Unfortunately, मेरे पास abstract class और interface में plain old inheritance का difference explain करने का समय नहीं है।

इस particular course में, यह कुछ ऐसा है जिसमें मैं अपने अन्य video courses में बहुत clearly जाता हूं और explain करता हूं।

लेकिन जो मैं करूंगा वह है explain करना कि हम यहां abstract class क्यों उपयोग कर रहे हैं instead of interface।

Reason क्यों हम abstract class उपयोग करना चाहेंगे वह है situations के लिए जब हम behavior share करना चाहते हैं।

तो उदाहरण के लिए, हम एक function stub या abstract function लिखने जा रहे हैं, जिसे मैं उन किसी भी क्लास में shared करना चाहता हूं जो BaseLogic से inherit करती है।

And मैं एक variable भी share करना चाहता हूं, लेकिन यह particular variable protected होना होगा as opposed to public।

And अगर हम इसे interface का उपयोग करके करने की कोशिश करते, तो necessarily वह particular value public होती।

हम एक generic type भी उपयोग करने जा रहे हैं।

तो मैं आपको दिखाऊंगा कि यह कैसे करना है।

तो generic type के लिए syntax है just angle brackets उपयोग करना।

And फिर आप उन angle brackets के बीच quite literally जो चाहें ले सकते हैं।

But मेरा suggestion आपको है कि ऐसा कुछ use न करें जो already used है, hence क्यों मैं यह all capitals EVENT उपयोग कर रहा हूं।

अब, अगर यह sense नहीं बनाता कि हम यहां क्या कर रहे हैं, तो यह more sense बना देगा जब हम उन classes को लिखेंगे जो BaseLogic से inherit करती हैं।

चलिए इसे finish off करते हैं।

इस abstract class के intent को briefly explain करने के लिए।

Basically, मैं कह रहा हूं कि मुझे classes का एक set चाहिए, वे जो BaseLogic से inherit करेंगे, उन सभी के पास यह function onEvent होगा।

दूसरे शब्दों में, ये classes user interface से events को handle करेंगी।

And फिर जैसा हम देखेंगे, हम इस Job object का उपयोग करने जा रहे हैं जो coroutines API से आता है child coroutines को cancel करने के तरीके के रूप में।

And इनमें से प्रत्येक logic classes को अपना खुद का coroutine scope बनाने के लिए भी।

मैं उसे explain करूंगा जब हम tutorial के उस particular भाग पर पहुंचेंगे। कॉमन पैकेज पर right-click करें और एक new Kotlin file create करें, जो just एक plain old file होने जा रहा है, और यह Extensions कहलाएगा।

Kotlin के extension functions और extension properties मेरी favorite features में से हैं language as a whole।

Without getting too technical here, extensions आपको Open-Closed Principle employ करने की अनुमति देते हैं, जो कहता है कि software entities open for extension होनी चाहिए, लेकिन closed for modification।

अगर यह sense नहीं बनाता, तो worry न करें यह kind of एक confusing definition है।

लेकिन यह हमें allow करता है existing source code में new functionality add करने के लिए बिना original source code को modify किए।

अब, यह particular file Extensions.kt kind of एक replacement है static utilities के लिए जो हम Java में या ऐसा कुछ उपयोग करते रहे होंगे।

यह really just एक जगह है जहां आप utility code stick करते हैं जो across the application used होता है।

चलिए हमारा पहला extension function लिखते हैं यह देखने के लिए कि यह कैसे काम करता है।

इस particular extension function का purpose, obviously यह activities के अंदर उपयोग किया जाएगा, really just syntactic sugar है, यह एक तरीका है ताकि मुझे Toast.makeText type न करना पड़े और message, Toast.LENGTH_LONG, और .show supply न करना पड़े।

इसके बजाय, उन activities में जहां हम इस particular extension function का उपयोग करेंगे – activities, मुझे कहना चाहिए जहां हम इसका उपयोग करेंगे, हम just makeToast type कर सकते हैं, उसे जो भी string हम display करना चाहते हैं दें, और यह handled हो जाता है like that।

इसे Activity class का extension function बनाकर, मैं इसे seamlessly किसी भी activity में उपयोग कर सकता हूं।

चलिए एक और much uglier utility extension function लिखते हैं।

इस ugly little function का purpose यहां दिए गए puzzle के elapsed time को लेना है जिस पर user currently काम कर रहा है, और इसे minutes और seconds के based पर एक value में convert करने का attempt करना है या एक string display करने के लिए minutes और seconds के based पर।

अब, अगर user को एक घंटे से ज्यादा लगता है, तो हम just generic "59:59 से अधिक" display करते हैं।

अब अगर आपको लगता है कि यह कोड Kotlin में ugly है, तो मैं आपको challenge करता हूं इसे Java में लिखने के लिए।

अब बिगिनर्स के लिए, यह intuitively sense नहीं बना सकता, लेकिन यह समझना important है कि this क्या refer कर रहा है।

यह actually उस Long object को refer कर रहा है जिस पर हम .toTime() call करने जा रहे हैं।

वह थोड़ा more sense बना सकता है जब हम actually इस particular extension function का उपयोग करने पहुंचेंगे।

केवल एक और extension है जिसे हमें add करने की जरूरत है, और यह actually एक extension property होने जा रहा है इस बार।

तो जो मैं यहां कर रहा हूं वह है Alt+Enter hit करना इस particular red thing पर, और फिर Add remaining branches hit करना।

फिर से Alt+Enter hit करना, हमारे इन्हें import करने के लिए, ये obviously string resources हैं।

वह एक चीज है जिसे हम hand से write नहीं करने जा रहे।

तो hopefully, जो आपने किया है वह है आपने starting point के लिए source code grab किया है, जिसमें string resources जैसी चीजें शामिल हैं। कॉमन पैकेज पर right-click करें फिर से, और हम एक new Kotlin interface create करने जा रहे हैं, जिसे DispatcherProvider कहेंगे।

यह interface बहुत small है – हम code लिखेंगे और फिर मैं briefly explain करूंगा कि यह क्या करता है।

अब, unfortunately, मैं briefly explain नहीं कर सकता कि coroutine context क्या है।

लेकिन मैं इस particular class के purpose को explain कर सकता हूं और हम इन coroutine contexts का उपयोग कैसे करने जा रहे हैं।

तो most situations में, जो काम हम coroutines land के अंदर करने जा रहे हैं वह main thread या UI thread पर होने वाला है।

अब, इसके साथ कहा जा रहा है, कुछ operations हैं जैसे file में write करना, जो हम actually main thread पर occur नहीं करना चाहते।

And वह एक situation होगी जहां हम IO context provide करने जा रहे हैं।

अब, इस particular interface का actual purpose really key है यहां।

हम जो करने जा रहे हैं वह यह है कि अगर हम hypothetically किसी class को test करना चाहते थे जिसे इन coroutine contexts का उपयोग करने की आवश्यकता है, एक JVM environment में, तो एक actual running application नहीं, तो हम क्या कर सकते हैं वह यह है कि हम एक particular kind का coroutine context return कर सकते हैं, जो हमें उस particular environment में test करने की अनुमति देता है।

मुझे पता है कि यह बहुत technical detail है, लेकिन मैं इसे whole lot simpler नहीं बना सकता।

हालांकि, इस interface का उपयोग करके, जब हम अपने coroutines को production environment में उपयोग करना चाहते हैं, तो हम real UI main thread context provide कर सकते हैं front end के लिए, और फिर हम real dispatcher provide कर सकते हैं IO thread के लिए।

इसे even simpler बनाने के लिए – हम really just कोड को test करना आसान बना रहे हैं।

कॉमन पैकेज पर right-click करें, New Kotlin File या Class पर जाएं, इस बार, यह एक object होने जा रहा है।

And hopefully मैं इसे right spell कर सकता हूं – ProductionDispatcherProvider।

फिर से, हम यहां code लिखेंगे और फिर explain करेंगे कि यह बाद में कैसे काम करता है।

मैं फिर से Alt+Enter hit करने जा रहा हूं।

And यह वह जगह है जहां हम actual dispatchers return करेंगे जिन्हें हम production में उपयोग करने जा रहे हैं as per इस particular object का नाम।

अब कई reasons हैं क्यों मैं यहां object keyword उपयोग कर रहा हूं।

तो basically objects Kotlin में इस particular case में Singletons हैं।

तो basically इसका मतलब है कि हम केवल ever एक ही ProductionDispatcherProvider software thingy को memory space में एक particular time पर float करते हुए रखेंगे।

वे thread-safe भी हैं, जो important है क्योंकि हालांकि coroutine necessarily एक thread नहीं है, हमारे Dispatchers.Main और Dispatchers.IO का threading से कुछ लेना-देना है।

And दूसरी चीज जो एक object कर सकता है वह यह है कि यह actually एक interface से inherit कर सकता है।

अब हम actually इस particular application में कोई unit tests नहीं लिखने जा रहे हैं जिनके लिए dispatchers की आवश्यकता हो, लेकिन just आपको दिखाने के लिए कि आप क्या करेंगे अगर आप कुछ class को unit test करना चाहते थे जिसे इन coroutine contexts का उपयोग करने की आवश्यकता है – आप क्या कर सकते हैं वह है आप just Dispatchers.Unconfined return कर सकते हैं, और फिर आप उसे दोनों IO context और UI context के लिए return करेंगे।

And वह है जो आप जैसे JVM JUnit test environment में उपयोग करेंगे।

पर्सिस्टेंस पैकेज में वे classes और functions होते हैं जिनकी role data को Android process के lifecycle से beyond persist या store करना है।

अगर आप नहीं जानते कि process क्या है, तो यह simply एक program है जो एक device पर running है।

Practically speaking, हम user द्वारा current Sudoku game में की गई progress को store करेंगे, साथ ही उस game के लिए settings, और user के personal records या statistics, जैसा मैं उन्हें call करता हूं।

यहां persistence पैकेज के architecture पर एक quick look है।

इस स्थिति में GameRepository दो data sources के लिए एक back-end decision maker के रूप में function करता है, जो data sources को खुद coordinate करता है।

Just CRUD operations carry out करने की कोशिश करें – create, read, update, delete, और या तो success या failure report करें अगर exception thrown होता है।

यहां general principle है – चीजों को साथ रखें जो साथ रखने में sense बनाते हैं, जो साथ रखने की जरूरत नहीं उन्हें separate करें, और एक abstraction या interface का उपयोग करें।

किसी भी जगह जहां implementation बदल सकता है – मैं local file storage या Proto DataStore उपयोग करना बंद करने का निर्णय ले सकता हूं।

तो इन details को रिपॉजिटरी से hide करना over-engineering नहीं है, बल्कि एक calculated decision है।

Data sources या storage mechanisms की बात करते हुए, हम अपने data को store करने के लिए दो different mechanisms का उपयोग करेंगे।

पहला, हम user के preferred Game Settings और उनके personal statistics को Proto DataStore में store करेंगे – DataStore इस तरह के data को store करने के लिए एक lightweight और efficient तरीका provide करता है Protocol Buffers का उपयोग करके।

Protocol Buffers एक serialization language है JSON के similar।

हालांकि, मैं personally इसे JSON से easier to read पाता हूं।

And fortunately, हम जिस library का उपयोग करेंगे वह अपने खुद के protobuf compiler के साथ आती है जो कुछ boilerplate code generate करेगी जिसे हमें otherwise खुद लिखना पड़ता।

हम device के file storage का उपयोग भी करते हैं user की currently active game में progress को store करने के लिए।

हर Android app को files store करने के लिए कुछ memory space दी जाती है, जो हम उपयोग करेंगे।

यह किया जाता है सभी domain models को Serializable implement करके।

And Java के input और output streams का उपयोग करके Kotlin भाषा से objects को read और write करके।

तो case में आप tutorial के साथ follow along नहीं कर रहे, और आपने starting point repository download नहीं की है, तो आप क्या करना चाहेंगे वह है आप एक directory add करना चाहेंगे जिसे proto कहते हैं main source set में, starting point repository में already वह directory होनी चाहिए।

तो just आगे बढ़ें और उस पर right-click करें, और new file पर जाएं।

And यह फाइल game_settings.proto कहलाएगी, और सुनिश्चित करें कि यह all lowercase है।

आगे बढ़ें और इसे file के top पर type करें।

तो Protocol Buffers essentially एक serialization language की तरह हैं।

यह JSON के बहुत similar है।

अगर आप इसमें more look करना चाहते हैं, तो आप JSON जैसी किसी चीज के benefits और pros and cons के बारे में पढ़ सकते हैं।

But personally, यह only project है जिसमें मैंने Protocol Buffers को अब तक use किया है, मैं इससे quite happy हूं।

Okay, तो चलिए just दो और lines add करते हैं।

And मैं वहां से कुछ और explain करूंगा।

Okay, तो चलिए थोड़ा और बात करते हैं जो हमने यहां just किया।

तो हमने एक message define किया है, जो Protocol Buffers में kind of एक data type या fields का collection है lack of a better term के लिए।

And हमने दो चीजें की हैं।

तो GameSettings message के अंदर, हमारे पास एक 32-bit integer है, जैसे एक तरह का smaller integer जो Sudoku puzzle के boundary को represent करता है।

तो जब मैं boundary कहता हूं, तो मेरा मतलब है जैसे 4x4 Sudoku puzzle का boundary 4 होगा, 9x9 Sudoku puzzle का boundary 9 होगा, obviously।

And दूसरी चीज जो हमने यहां की वह है हमने Protocol Buffers में एक enum define किया।

अब जब आप ये enums create कर रहे होते हैं, तो आपको एक default value unknown की जरूरत होगी।

And फिर आपके पास other values हैं जो enum potentially हो सकती हैं।

Also notice कैसे boundary और difficulty में, enum के ऊपर fields में, मैं इसे default values दे रहा हूं, naturally वे वैसे ही values होंगे जैसे protocol buffer pre-loaded हो जाता है, जैसे first time आप उसे access करते हैं।

अब, यहां समझने के लिए important चीज यह है कि assuming आपने Protocol Buffers के लिए support add कर दिया है अपने build.gradle configuration में, protobuf compiler actually कुछ Java files या classes generate करने जा रहा है इस particular message से।

Okay, तो जो मैं यहां कर रहा हूं वह है मैंने completed project खोल लिया है, और मैं just उस file को देख रहा हूं जो protocol buffer compiler द्वारा generate की गई थी।

And मैं बस चाहता हूं कि आप यहां notice करें कि जब आप Proto DataStore उपयोग कर रहे होते हैं, तो क्या होने वाला है वह actually एक Java class generate करने वाला है आपके लिए।

Obviously, आप देख सकते हैं हमारे पास GameSettings camelCase में है, जो हमने अपने message के रूप में define किया था।

And फिर हमारे पास वह enum भी defined है below।

तो यह actually हमारे लिए क्या करता है – basically, यह हमें allow करने वाला है serialize करने के लिए या basically translate करने के लिए Java से Protocol Buffer language में और vice versa।

And इसका यह भी मतलब है कि हमें actually अपना खुद का plain old Java object create करने की आवश्यकता नहीं है ऐसा करने के लिए।

Library वह हमारे लिए generate करने जा रही है।

But हम अभी भी इसे अपने कोड में उपयोग कर सकते हैं, जो हम एक moment में करेंगे।

हम एक और proto file add करने जा रहे हैं।

तो आगे बढ़ें और protobuf directory खोलें, right-click फिर से, file पर जाएं।

And यह वाला user_statistics.proto कहलाएगा।

Alright, तो मैंने just पहले तीन lines को दूसरे protocol से copy और paste किया है क्योंकि हम उन्हें reuse करने जा रहे हैं।

And हम यहां एक और message create करने जा रहे हैं।

अब जब मैं statistics कहता हूं, तो यह kind of मेरा तरीका है user के personal records के बारे में बात करने का।

तो किसी particular size और difficulty के Sudoku puzzle को solve करने में user द्वारा बनाए गए shortest times to completion क्या हैं? यह pretty straightforward है।

तो चलिए just इसे लिख देते हैं।

And there you have it।

अब, आप wonder कर रहे होंगे कि मैं यहां 64-bit integers क्यों उपयोग कर रहा हूं।

तो ये actual values milliseconds में store होने जा रही हैं, इसीलिए मैं 64-bit integer storage वहां चाहता हूं instead of 32-bit integer।

मैं actually 100% sure नहीं हूं कि यह necessary है, लेकिन मैंने just to be safe ऐसा किया, और realistically, यह really उतना extra memory eat up नहीं करने वाला।

Okay, तो वह है हमारे protocol buffer files के लिए।

अब, हमें कुछ Protocol Buffer DataStores create करने होंगे, जो है कैसे हम actually अपने protocol buffers को create और access करने जा रहे हैं।

आगे बढ़ें और persistence पैकेज पर right-click करें, New Kotlin File या Class पर जाएं और यह just एक file होने जा रही है जिसे DataStores कहते हैं।

Okay, तो आगे बढ़ने से पहले, आप Build पर जाना चाहेंगे और Make Project करेंगे।

अब build probably fail करेगा, लेकिन हम सभी really चाहते थे वह है appropriate Java class को protocol buffer से generate करना।

But अगर किसी reason से वह आपके लिए काम नहीं करता, तो just follow along करें, और eventually यह काम करेगा।

Okay, तो प्रत्येक protocol buffer based data source के लिए, हमें एक तरीका provide करने की जरूरत है उसे context से get करने या create करने का, फिर दूसरी चीज जिसकी हमें जरूरत होगी वह है एक serializer।

आगे बढ़ें और सब कुछ import करें।

And delegate में दो चीजें हैं जिन्हें हमें add करने की जरूरत है।

Okay, तो worry न करें कि यह red दिख रहा है – हम actually इस serializer को लिखेंगे।

Next, तो मैं just explain करना चाहता था कि यहां क्या हो रहा है।

तो हम एक DataStore object create कर रहे हैं, और यह protocol buffer generated Java class लेता है, जिसे GameSettings कहा जाता है।

And essentially, जो यह करता है वह है एक reference create करता है जिसे हम either अपने protocol buffer को store या retrieve करने के लिए उपयोग कर सकते हैं।

अब, आप wonder कर रहे होंगे कि game_settings.pb क्या है, और क्यों इसका एक different file extension है हमारे proto files से, to the best of my understanding game_settings.pb कुछ ऐसा है जो compiler द्वारा after the fact generate किया जाता है, जबकि proto file वह है जो हम compiler के लिए consume करने के लिए लिखते हैं।

But case में मैं उस पर wrong हूं, तो feel free to flame me on Twitter।

दूसरी चीज जिसकी हमें जरूरत होगी वह है एक serializer, जो serialization का ख्याल रखता है quite obviously।

इसके बाद, आप यहां click कर सकते हैं, Alt+Insert hit करें, Override methods, और हमें केवल serializer interface से methods की जरूरत है।

तो फिर से, चलिए code पढ़ते हैं और फिर मैं explain करूंगा जो मुझे after the fact explain करने की जरूरत है।

Okay, तो मैं details को यहां pretty light रखने जा रहा हूं।

तो obviously, जब हम अपना data store create करते हैं, यह GameSettingsSerializer यहां दिया जाता है।

And जो serializer करता है वह है हमें input streams से read और write करने में help करता है।

तो दूसरे शब्दों में, हम obviously एक protocol buffer file से read करने जा रहे हैं, और फिर वह serialized या rather deserialized होगा Java में, और vice versa।

तो basically, जो Android team ने हमारे लिए यहां किया है वह है उन्होंने things जैसे error handling और input streams के साथ deal करना बहुत आसान बना दिया है।

क्योंकि अगर आपने कभी Java में input streams के साथ काम किया है, तो आप बता सकते हैं कि वहां, आप जानते हैं, आप शायद उससे related बहुत सारे boilerplate code से familiar हैं।

तो basically, हम यहां थोड़ा boilerplate work करते हैं।

And यह translate होता है एक very simple API में जब हम actually इस particular tool के साथ back end में read और write करना चाहते हैं, जो हम एक moment में करेंगे।

Okay, अब obviously हमें एक और data store और serializer लिखने की जरूरत है other data type के लिए भी।

तो यह उन rare scenarios में से एक होगा जहां मैं actually just copy और paste करता हूं क्योंकि वहां absolutely कुछ भी new नहीं है, हम just एक couple of words change करने जा रहे हैं।

तो यह उन points में से एक होगा जहां मैं आपको encourage करूंगा complete source code को side में open रखने के लिए और फिर उस way, आप थोड़ा copy-paste action कर सकते हैं, जैसे मैं अब करने जा रहा हूं।

And वह है हमारा DataStores file complete।

अब obviously, अगर आपके पास इनमें से एक whole bunch होते, तो आप probably separate files उपयोग करना चाहेंगे, लेकिन चूंकि मेरे पास केवल दो हैं, मैंने just उन्हें same file में stick करने का decide किया।

Persistence पैकेज पर right-click करें और New Kotlin Class पर जाएं।

यह वाला LocalGameStorageImpl कहलाएगा।

तो firstly, हम एक constant create करने जा रहे हैं जो उस text file के name को represent करेगा जिसे हम game data को read और write करने के लिए उपयोग करेंगे।

Next, हम constructor create करेंगे।

तो आप wonder कर रहे होंगे कि file storage directory कहां से आती है।

जब हम इस एप्लिकेशन का build logic create करते हैं, जो kind of मेरा inversion of control dependency injection टाइप stuff है, क्या होने वाला है वह यह है कि हम Android system पर इस एक function को call करने जा रहे हैं, जो हमें system से specific directory return करेगा जहां हम files जैसी चीजें read और write कर सकते हैं।

चलिए interface को implement करते हैं।

अब, मैं इसे relatively quickly get through करने की कोशिश करने जा रहा हूं।

But एक चीज जो मैं explain करना चाहता हूं वह यह है कि आप notice करेंगे कि मैं helper functions का fairly extensive usage कर रहा हूं।

इसका reason just redundant code avoid करना है।

Also, अन्य implementations के साथ, हम withContext coroutine builder का उपयोग करने जा रहे हैं इस तरह के IO work को main thread से off करने के लिए।

तो हम क्या करेंगे वह है एक helper function call करेंगे जिसे updateGameData कहते हैं, और हम उसमें game data pass करेंगे।

And अगर वह operation successful होता है, तो हम actually just वही game object return करेंगे जो pass in किया गया था क्योंकि वह consistent होना चाहिए।

Okay, अब हम helper create कर सकते हैं।

तो यहां, हम exception throw करने जा रहे हैं ताकि वह actually उन functions के catch block द्वारा pick up हो जाए जो इस helper को call करेंगे।

अब, हम input और output streams का उपयोग करने जा रहे हैं, जो Java standard library का हिस्सा हैं, हमारे data को file से और से read और write करने के लिए।

अगर आप wonder कर रहे हैं कि यह word stream kind of क्या mean करता है, ultimately, जो हम actually कर रहे हैं kind of low level पर, वह है हम अपने game या Sudoku puzzle object को ले जा रहे हैं, और हम इसे serialize करने जा रहे हैं basically एक stream या एक very long sequence of textual characters में।

And वह है जो हम actually file से read और write करेंगे।

Okay, तो दो points – आप always अपने streams को close करना चाहते हैं।

Also, आप wonder कर रहे होंगे, कैसे यह है कि हम .writeObject कह सकते हैं और अपना Sudoku puzzle pass in कर सकते हैं, लेकिन चलिए just parameters यहां check करते हैं।

तो मैं Ctrl+P hit करने जा रहा हूं parameter brackets के अंदर, और जैसा आप देख सकते हैं, यह any type accept करता है।

अब important चीज यह है कि अगर हमारी different classes जैसे SudokuPuzzle और SudokuNode Serializable extend नहीं करतीं, तो हम इसे errors के बिना नहीं कर पाएंगे।

तो updateNode के लिए, यह थोड़ा different है, हम just एक individual node update कर रहे हैं।

तो यह कैसे काम करने जा रहा है वह यह है कि हम old data लेने जा रहे हैं और फिर हम just उस individual node को update करने जा रहे हैं।

And फिर हम result को वापस storage में rewrite करेंगे।

तो getGame एक और helper होगा जिसे हम लिखेंगे, और जो मैं करने जा रहा हूं वह है actually उसे right away लिखना।

Otherwise, autocomplete और error handling stuff all over the place होगा।

Okay, वह है जो हमें वहां करने की जरूरत है।

अब, just एक quick reminder यहां – जब हम color कहते हैं, और really, जब भी कोई graph data structure में color के बारे में बात करता है, वे really just एक number के बारे में बात कर रहे होते हैं।

तो इस case में, number particular Sudoku square में placed actual value को represent करता है।

तो यह होगा जैसे one through nine, या one through four, depending on Sudoku के boundary पर।

Also elapsed time को update करेंगे।

इसके updated होने के बाद, हम उस result को storage में write करेंगे hopefully।

And just front end को everything else के साथ synchronized रखने के लिए, फिर हम वही game object return करेंगे।

अब यह just मेरे attention में आया है कि मैंने एक particular integer जिसे color कहते हैं इस particular function में add करना भूल गया जब मैंने इसे लिखा था, तो चलिए just आगे बढ़ते हैं और उसे अभी fix करते हैं।

There we go।

And मैं easiest के लिए last में manage कर गया।

And वह है इस फाइल के लिए।

Persistence पैकेज पर right-click करें, New Kotlin Class पर जाएं, यह वाला GameRepositoryImpl कहलाएगा।

तो case में आप ahead jump कर गए, और आप actually रिपॉजिटरी पैटर्न से familiar नहीं हैं, मैंने actually उसे already explain किया था इस series के part two में जहां मैंने डोमेन पैकेज बनाया था।

किसी भी case में, मुझे just reiterate करने दें, reiterate इस particular class का purpose – यह basically एक bridge और decision maker है back end के लिए।

अब sometimes आपके पास multiple different repositories या datasets होंगे back end में, और उन्हें separate रखना एक good idea हो सकती है।

Reason क्यों मैंने इस particular case में नहीं किया वह है क्योंकि game storage और settings storage actually inextricably linked हैं।

वे by nature closely related हैं।

So based on that, और इस तथ्य पर कि यह actually बहुत large application नहीं है, मैंने उन्हें इस रिपॉजिटरी के अंदर together रखने का choose किया।

And फिर यह कैसे काम करेगा वह यह है कि रिपॉजिटरी इन दो different data sources को coordinate करेगी।

चलिए constructor और रिपॉजिटरी interface से शुरू करते हैं।

Okay, तो जैसा आप देख सकते हैं, हमारे पास हमारे लिए work cut out है।

तो जो मैं करने जा रहा हूं वह है मैं code को relatively quickly लिखने की कोशिश करने जा रहा हूं।

And इसके लिखे जाने के बाद, मैं explain करूंगा कि यह क्या करता है।

तो वहां कुछ भी new नहीं होना चाहिए इस particular function में, except इस fact के लिए कि हम एक assignment statement within a control statement कर रहे हैं – val currentGameResult = etc।

We're allowed to do that because Kotlin एक beautiful और idiomatic language है।

यह वाला actually pretty simple है।

आप जानते हैं, for the life of me, मैं नहीं समझता कि यह top पर error क्यों डालता रहता है।

मैं इस function को एक moment में explain करूंगा।

So puzzleIsComplete actually एक function है जो computation logic package में exist करता है, जिसे हम later on लिखेंगे, of course, और वह exactly वही करता है जो वह कहता है।

But वह either true या false return करेगा based on whether puzzle complete है या नहीं।

Hence isComplete।

Okay, तो जो मैंने यहां किया है वह है मैंने plain language use case को copy और paste किया है जो इस particular function को describe करता है।

अब, जैसा आप देख सकते हैं, यह pretty complicated है एक basic explanation देना जो क्या हो रहा है।

And why did this – जब हम current game request करते हैं, यानी जब application starts up, वहां number of different चीजें हैं जो हो सकती हैं।

तो starters के लिए, user के पास currently active game हो सकता है और वे just उसे retrieve करना चाहते हैं।

यह application का first run हो सकता है, तो currently storage में कोई game exist नहीं करता।

And फिर वहां different situations हैं जहां errors occur कर सकते हैं along the way।

यह कुछ ऐसा है जो होता है जब आप multiple different data sources coordinate कर रहे होते हैं।

अब मेरे पास इन different event streams को track करने का मेरा अपना system है – मैं basically letters और numbers का उपयोग करता हूं steps और different potential event streams को denote करने के लिए।

But जो भी आप करते हैं, मेरा suggestion आपको है इसे plain language में पहले लिखें और फिर code लिखने जाएं।

यही मैंने किया यह comment जो आप ऊपर देख रहे हैं, मैंने उसे code लिखने से पहले लिखा था।

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

Okay, तो हमारे पहले Event Stream के लिए, हम current game retrieve करने का attempt करते हैं, और वह successfully return हुआ।

And फिर हम यह भी जानना चाहते हैं कि current game complete है या नहीं।

हम just onComplete से छुटकारा पा सकते हैं।

And here we go again।

तो यह obviously वह case है जहां user ने first time application load किया है और हम एक brand new game create करना चाहते हैं।

And looks like मुझे इसे manually इस समय करना होगा।

The autocomplete मेरी यहां मदद नहीं कर रहा।

But fairness में, हमने वह function अभी तक नहीं लिखा है।

Okay, मैं just double-check करने जा रहा हूं कि मैंने उसे correctly लिखा है।

अब, इससे पहले कि मैं आगे बढ़ना चाहूं, मैं एक चीज explain करना चाहता हूं software architecture पर अपने perspective के बारे में।

While sometimes एक simpler application में, हम कुछ ऐसा कर सकते हैं जैसे presenter को different repositories या back-end data sources coordinate करने देना।

इस particular case में, वहां enough complicated back-end logic था कि मैं also एक decision maker class चाहता था, जो हुआ वह यह GameRepositoryImpl back end पर था, और इस class का part of the purpose है back-end data sources को coordinate करने के logic का ख्याल रखना, ताकि मैं presentation logic class को वह करने दे सकूं जो उसे करना supposed है – presentation logic manage करना, और फिर मेरे पास यह class है जो इस messy तरह के almost business logic टाइप stuff को यहां deal करती है।

Anyways, हम अभी done नहीं हैं।

Okay, तो यह just मुझे occur हुआ कि मैंने IGameRepository के interface में एक function miss कर दिया है।

तो चलिए just आगे बढ़ते हैं और उसे add करते हैं।

तो जो मैं करने जा रहा हूं वह है मैं just updateGame को copy करूंगा, उसे नीचे paste करूंगा।

And जो हम इसे call करने जा रहे हैं वह है createNewGame।

And यह एक Settings object लेगा और वह है।

तो वह actually एक helper function है जिसे मैंने mostly legibility के लिए create किया था, चलिए just आगे बढ़ते हैं और उसे अभी add करते हैं।

Just एक और quick note यहां – आप notice करेंगे कि मुझे incredibly long और descriptive names पसंद हैं हर उस चीज का जो चल रहा है।

यह largely इसलिए है क्योंकि मुझे fine details के लिए great memory नहीं है।

तो इन चीजों को super long और descriptive बनाकर, मुझे actually उन्हें remember नहीं करना पड़ता, मैं just अपना कोड पढ़ सकता हूं और pretty much समझ सकता हूं कि वह क्या करता है।

Even इन complicated situations में जहां हमारे पास ये सभी different event streams और interactions occur हो रहे हैं।

Okay, only two more short functions to go।

And वह है हमारे back end के लिए।

UI पैकेज के top level पर, हमारे पास चार small files हैं, जिनका उपयोग हम styles, colors, fonts, और so on create और apply करने के लिए करेंगे।

उन फाइलों में से एक हमारे application के लिए global theme है।

And मैं आपको दिखाऊंगा कैसे एक light और dark theme दोनों create करें ऐप के लिए केवल a few extra lines of code में।

इस section के end के लिए stay tuned, क्योंकि मैं different themes का live demo करूंगा।

UI पैकेज पर right-click करें और एक new Kotlin file create करें, जिसे Color.kt कहेंगे।

यह file essentially colors.xml के लिए एक replacement होगी, अगर आप old resources system के साथ काम करने के used to हैं जो XML में based था।

चलिए एक Color object create करते हैं।

Compose Color class import करना सुनिश्चित करें।

Okay, तो आगे बढ़ने से पहले, यहां समझने के लिए सबसे important चीज यह है कि इन particular values को कैसे read करें।

तो यहां पहले दो characters 0x।

यह basically compiler को बताता है, जो वह program है जो इस कोड को read करेगा, कि यह in fact एक hexadecimal number है।

दूसरे दो digits यहां alpha value को percentage के रूप में indicate करते हैं।

Alpha transparency या opacity को कहने का दूसरा तरीका है।

बचे हुए तीन pairs red, blue, और green या RGB values हैं, फिर से hexadecimal percentage में, और वह pretty much वह सब है जो इन different color values के बारे में जानने के लिए है।

मैं rest of the values को copy और paste कर रहा हूं क्योंकि absolutely no point है either उन सभी को type करने में।

But also keep in mind कि उनके पास कुछ predefined values हैं जैसे Black, उदाहरण के लिए, जिनका आप भी उपयोग कर सकते हैं।

UI पैकेज पर right-click करें, और हम एक और new Kotlin file create करने जा रहे हैं।

And यह वाला Shape कहलाएगा।

तो old view system में, जब आप कुछ ऐसा करना चाहते थे जैसे rounded corners के साथ background create करना, या एक button या widget या ऐसा कुछ, आपको usually drawable folder के अंदर कुछ create करना पड़ता था, जो XML based था।

फिर से, चूंकि यह Compose है, हम just आगे बढ़ सकते हैं और उसे Kotlin में कर सकते हैं।

Instead, हे, मैं just कुछ default parameters उपयोग करूंगा।

अब, यह आपका first time हो सकता है .dp extension देखने का, चलिए just source code पर एक quick look लेते हैं।

तो जैसा आप देख सकते हैं, आप basically just इसे integer, double, और विभिन्न kinds के numbers पर append कर सकते हैं।

यहां समझने के लिए important चीज यह है कि यह basically Compose framework को बताता है कि हम density-independent pixels उपयोग करना चाहते हैं।

अगर आप उन exactly क्या हैं उसका more profound explanation चाहते हैं, तो मैं strongly suggest करता हूं आप उसमें look करें क्योंकि यह थोड़ा complicated है।

Suffice it to say कि idea यहां है framework को heights और widths और ऐसी चीजों के लिए values create करने की अनुमति देना जो variety of different screen sizes और form factors के across work करें।

UI पैकेज पर right-click करें, और हम एक और Kotlin file create करने जा रहे हैं, यह वाली Type कहलाएगी।

अब case में आप wonder कर रहे हैं, जब हम type कहते हैं, तो हम really type system के बारे में बात नहीं कर रहे, या type theory से कुछ लेना-देना नहीं है, इसका typeography या text से लेना-देना है और यह text कैसे styled या presented होता है।

तो फिर से, यह बहुत much उसी तरह की चीज है जो हम styles.xml में किया करते थे – हम basically different text styles का एक bunch create करने जा रहे हैं, जिनका हम throughout the application उपयोग करेंगे।

And फिर हम kind of देखेंगे कि उनको Typography object में कैसे wrap करें।

And फिर देखेंगे कि उस Typography object को हमारे sort of global Compose theme में कैसे add करें।

पहले, चलिए एक TextStyle create करते हैं।

Sometimes हमारे पास एक situation होती है जहां हम एक bunch of default values रखना चाहते हैं, लेकिन हम might want एक या दो values जो actually parameter के रूप में pass in होते हैं TextStyle object create करने के लिए।

तो मैं आपको एक और तरीका दिखाऊंगा इन्हें create करने का function का उपयोग करके।

Just gonna कुछ quick copy-paste यहां करें।

And फिर हम color को override कर सकते हैं।

तो फिर से, जो मैं इन बाकी text styles के लिए करने जा रहा हूं अब जबकि हमने यहां सब कुछ देख लिया है वह है मैं उन्हें copy और paste कर दूंगा।

But एक और चीज है जो new है जिसे हमें इस particular file में create करने की जरूरत है।

Okay, जैसा आप देख सकते हैं, हमारे पास यहां एक couple different text styles हैं।

तो आखिरी चीज जो हमें करने की जरूरत है वह है Typography object create करना।

तो basically, जो वह mean करने वाला है वह यह है कि हम उन text styles में से कुछ को assign करने जा रहे हैं जो हमने नीचे create किए हैं, जो common चीजों में उपयोग होते हैं जैसे किसी particular feature का body text, buttons, titles, उस तरह की चीजें।

अगर वह sense नहीं बनाता।

चलिए just code लिखते हैं।

Compose.Material select करना सुनिश्चित करें, Kotlin.Text नहीं।

Okay, हम just दो और करने जा रहे हैं।

All right, और केवल एक और चीज जो हमें करने की जरूरत है वह है GraphSudokuTheme को set up करना।

UI पैकेज पर right-click करें, और हमारे पास है, you guessed it, एक और Kotlin file।

And यह वाली GraphSudokuTheme.kt कहलाएगी।

तो Jetpack Compose के handy little features में से एक यह है कि light और dark modes के लिए theme create करना incredibly easy है।

जैसा कोई जो generally speaking, dark mode almost always उपयोग करता है actually really appreciate करता हूं Compose के इस particular feature को।

उस process में पहला step है दो different color palettes create करना।

चलिए light color palette से शुरू करते हैं।

इनमें से कुछ properties probably most Android developers को familiar होनी चाहिए जैसे colorPrimary होना।

That's how we used to do it also old XML system में colors के साथ, या कम से कम वह एक common naming convention थी।

अब, एक चीज जो मैं यहां point out करना चाहता हूं वह यह है कि इनमें से कुछ more obscure ones जैसे primaryVariant, surface, onPrimary, और so forth के लिए एक degree है, मैं really just उन्हें उपयोग कर रहा हूं क्योंकि यह convenient है, वे necessarily किसी particular चीज को mean नहीं करते।

But important चीज जो यहां समझने की है वह यह है कि अगर light theme और dark theme के बीच किसी भी different color का अंतर है, तो हम उसे यहां कहीं define करना चाहते हैं, और फिर उसे appropriate composable में उपयोग करते हैं, जिसे हम later on सीखेंगे।

Okay, वह actually supposed to be uppercase था there by convention।

And also notice कि मैंने dark color palette को copy और paste कर दिया है, क्योंकि फिर से, वहां कुछ भी new नहीं चल रहा है।

Next step, however, बहुत important है – हम अपना theme create करने जा रहे हैं, और यह actually really, really easy होने वाला है।

यहां एक little shortcut जो मैंने एक friend से सीखा – अगर आप composable function really quickly create करना चाहते हैं comp type करना शुरू करें, और फिर Enter hit करें, just saves you a little bit of time।

अब यह theme दो parameters यहां रखने वाला है।

तो इससे पहले कि हम इस function का body लिखें, मैं just इन दो parameters पर discuss करना चाहता हूं।

तो जैसा आप देख सकते हैं, हम actually एक function call कर रहे हैं isSystemInDarkTheme, क्या होने वाला है वह यह है कि यह system call एक Boolean return करेगा, जो हमें बताएगा कि user ने specify किया है या नहीं कि app supposed to be in dark mode या light mode।

And फिर content represent करता है वह सब कुछ जो इस theme के अंदर wrap किया जाएगा।

यहां समझने के लिए important चीज यह है कि वह सब कुछ जो हम इस composable के अंदर डालते हैं, यानी content, उसे इन सभी different colors, styles, और typography information का access होगा theme के अंदर से।

इसकी actual utility तब बहुत more sense बनाएगी जब हम actually composables लिखेंगे।

बस चीजों को finish off करने के लिए, हम एक MaterialTheme composable create करने जा रहे हैं।

And हमें lambda expression की जरूरत नहीं होगी।

तो there you have it, यह केवल कुछ minutes लेता है जैसे color resources और styles और typography information create करने के लिए जो necessary हैं दोनों एक dark color palette और एक light color palette render करने के लिए different modes के लिए।

जो मैं करने जा रहा हूं वह है आपको एक quick demo दिखाना कि यह actually एक application में कैसा दिखता है।

यहां मैं application को light theme में start करने जा रहा हूं।

फिर मैं operating system settings पर navigate करूंगा और इसे preferred dark mode पर set करूंगा।

And upon returning हम immediately देखते हैं कि application अब dark theme उपयोग कर रहा है।

हम अब अपना user interface बनाना शुरू करने के लिए ready हैं।


UI Components पैकेज में user interface के reusable elements होते हैं।

चूंकि यह एक बहुत small app है, केवल दो ऐसे elements हैं – एक toolbar, और एक loading screen।

Compose की great features में से एक यह है कि हम अपने components को different ways में reusable बना सकते हैं।

पहला, अगर एक component को positioned करने की आवश्यकता है according to जहां वह different parent composables या parent screens में fit होता है, हम एक modifier pass in कर सकते हैं instead of एक modifier create करने के child composable के अंदर।

यह experiment करने worth है case में आपने already नहीं किया।

दूसरा, composables को arguments के रूप में pass in करना possible है, जो भी reuse और functionality के extension को allow करता है।

इस ऐप में, हम दो different UI screens के लिए different toolbar icons चाहते हैं।

And हम इसे उन parent UI screens से icon composables pass in करके achieve कर सकते हैं।

आप बाद में देखेंगे कि हम different icons और different click events को कैसे specify और handle कर सकते हैं।

Same toolbar का उपयोग करके – हम यह reusable loading screen भी create करेंगे और बाद में, मैं आपको दिखाऊंगा कि इसे animate कैसे करें।

UI पैकेज पर right-click करें और new package पर जाएं।

And यह वाला Components कहलाएगा।

Just एक brief explanation यहां – मैंने इस particular convention को Compose samples repository से adopt किया है।

तो जो इस particular folder में जाएगा वे composables होंगे जो ultimately reusable होंगे across a variety of different UI elements और different screens।

इस मामले में, हम एक reusable toolbar create करने जा रहे हैं, और एक reusable loading screen भी।

Components folder पर right-click करें, और New Kotlin File पर जाएं, और यह वाला AppToolbar कहलाएगा।

चलिए हमारा function stub create करते हैं – जो मैं करने जा रहा हूं वह है comp type करना और फिर autocomplete composable function create करेगा।

यह वाला AppToolbar कहलाएगा।

पहले, चलिए parameter list लिखते हैं और मैं इसे थोड़ा explain करूंगा।

Compose.UI Modifier select करना सुनिश्चित करें।

चलिए modifiers के बारे में थोड़ा बात करके शुरू करते हैं।

तो modifiers basically वह हैं कि कैसे आप इन styles, size, और position तरह के data को एक particular composable के लिए create कर सकते हैं।

अब वहां kind of दो different main ways हैं यह करने के।

हम could of course, इस modifier को create करें और इसे इस widget के अंदर उपयोग करें।

But वह एक situation होगी जब widget खुद उस तरह की information decide करने जा रहा हो।

चूंकि हम यहां एक reusable component उपयोग कर रहे हैं, एक AppToolbar, जिसे हम multiple different places में उपयोग करने की plan कर रहे हैं।

इस particular situation में, हम modifier को इस function में pass in करने जा रहे हैं, जो basically एक तरीका है यह कहने का कि parent composable actually decide करेगा कहां position करना है और कैसे size करना है इस particular UI element को।

Title pretty self-explanatory है, लेकिन जो थोड़ा more complicated है वह है icon।

And फिर से, वह किसी चीज द्वारा dictate किया जाएगा parent composable में।

That's how I actually make this thing reusable और allow इसे different icons या different actions handle करने के लिए जब यह clicked हो।

इस particular composable को finish off करने के बाद, मैं आपको actual icon का एक quick preview दिखाऊंगा जिसका हम उपयोग करने जा रहे हैं।

उम्मीद है वह थोड़ा more sense बना देगा।

पहली चीज जो हम करना चाहते हैं वह है TopAppBar composable को override करना।

चलिए just एक moment pause करते हैं और different colors के बारे में बात करते हैं।

तो इस problem को solve करने का एक तरीका होगा कुछ तरह का color यहां hardcode करना।

But पिछले section में इस tutorial के, हम दोनों light और dark theme set up करने के trouble से गुजरे।

तो जो हम यहां कर रहे हैं वह है हम actually एक color उपयोग करने जा रहे हैं जो theme पर based है।

याद रखें GraphSudokuTheme composable में, एक function call था जो isSystemInDarkTheme या ऐसा कुछ था।

And वह actually dictate करने वाला है कौन सा color palette हम select करते हैं।

तो MaterialTheme.colors.primary का उपयोग करके, यह automatically appropriate color inherit करेगा based on चाहे हम light या dark mode में हों।

And वह एक reason होगा यहां कुछ hardcode न करने का।

इस case में, हमारे पास एक color है जो same रहेगा चाहे light या dark mode हो।

तो हम just एक Text composable add करने जा रहे हैं जो effectively एक extra view है।

But अगर आप कुछ ऐसा add करना चाहते थे जैसे application के लिए logo front या title text के बाद, तो आप क्या कर सकते हैं वह है एक Row में add करें और फिर just दोनों icon और Text composable add करें।

And फिर आप ready to go होंगे।

आगे बढ़ें और import करें।

यह probably pretty self-explanatory है।

लेकिन जब हम particular fonts और stuff के लिए style data inherit करना चाहते हैं, तो यह है कि हम यह कैसे कर सकते हैं।

फिर से, यह कुछ super handy है।

And आप केवल Kotlin में इसे देखते हैं, certainly not Java।

तो जो हम यहां कर रहे हैं वह है हम explicitly पूछ रहे हैं कि क्या application currently light mode में है, और फिर हम उसके based पर एक text color pick कर रहे हैं।

यह really just एक alternative way है इस conditional UI logic को handle करने का बिना कुछ theme को specifically assign किए।

Next, हम alignment deal करेंगे।

And वह है हमारे toolbar में Text composable के लिए।

तो ActionBar शायद older Android developers के लिए more familiar होगा।

But basically इसे toolbar के अंदर icons की तरह सोचें।

Generally, वे very important actions के लिए उपयोग किए जाते हैं user interface में, जैसे new feature पर navigate करना, indicate करना कि आप कुछ करने के साथ done हैं।

And note importantly कि यह particular lambda function RowScope के type की है।

तो basically, इसका मतलब है, अगर आपके पास several action buttons हैं, तो आप उन्हें इन दो brackets के अंदर place कर सकते हैं, और वे automatically lined up हो जाएंगे जैसे एक row।

अब हमें बस icon type करना है और फिर यहां parentheses add करना है।

And यह क्योंकि हम actually इस icon को parent composable से pass in करने जा रहे हैं।

जैसा मैंने moments ago कहा, मैं just आपको icon का एक sneak preview देना चाहता था।

हम इसे अभी write नहीं करने जा रहे, लेकिन हम बाद में करेंगे।

The important चीज जो यहां समझने की है वह यह है कि हम decide कर रहे हैं onClick को कैसे handle करना है और यह चीज actually कैसी दिखती है parent composable में, हम actually इसे toolbar के अंदर नहीं कर रहे।

And उस responsibility को toolbar से बाहर खींचकर, वह है कैसे हम वह reusability पाते हैं जो हम चाहते हैं।

Components package पर right-click करें, New Kotlin File पर जाएं, और यह वाली LoadingScreen कहलाएगी।

चलिए हमारा LoadingScreen composable create करते हैं।

पहली चीज जिसकी हमें जरूरत होगी वह है एक Surface।

तो आप wonder कर रहे होंगे कि हम यहां Surface विशेष रूप से क्यों उपयोग कर रहे हैं – इस case में, मैं really just UI का एक space surface चाहता हूं जिसका एक particular color और specific dimensions हो।

यहां मैंने fillMaxHeight को 0.8f के fraction पर set किया है, जो basically कह रहा है कि मैं चाहता हूं यह user interface के most of the height को take करे।

But मैं कुछ space चाहूंगा किसी ऐसी चीज के लिए जैसे ad banner या उस nature की कोई चीज।

Anyways, मैं basically एक icon या image चाहता हूं जो एक progress bar के ऊपर stacked हो जो किसी तरह के text के ऊपर stacked हो।

तो उस तरह की situation के लिए, obviously हम Column उपयोग करना चाहेंगे।

Obviously, हम चीजों को center करेंगे।

आगे बढ़ें और import करें।

अब मैं notice कर रहा हूं यह improperly हमारे import कर रहा है मुझे लगता है Compose libraries के अंदर कुछ है जो basically हमारे mimics करता है तो let me just उन imports को fix करूं इससे पहले कि हम proceed करें।

जैसा आप यहां देख सकते हैं, मैंने just copy और paste किया है हमारे import में।

And अब हम good to go हैं।

वह है हमारा logo।

यहां है आपका progress bar।

Okay, तो आप wonder कर रहे होंगे इस painter thing के बारे में।

तो basically, Compose के alpha version में, हमें specify करना पड़ता था कि यह vector asset है या bitmap asset और stuff जैसे कि।

तो हम just इस generic painterResource thing का उपयोग कर सकते हैं और इसे basically हमारे drawable में किसी भी चीज पर point कर सकते हैं।

And यह actually figure out कर लेगा कि यह bitmap है या vector asset।

Also, मैं copy function यहां point out करना चाहता था।

Suppose आपके पास एक color है और आप slightly alpha value change करना चाहते हैं या आपके पास इन TextStyle objects में से एक है और आप किसी तरह का change करना चाहते हैं।

Copy function super handy है ऐसा करने के लिए।


ट्यूटोरियल के इस भाग में, हम event sealed class, ViewModel, और presenter create करेंगे active game feature के लिए।

इससे पहले कि हम वह करें, आइए इस architecture में involve कुछ design decisions को देखें।

हमारे प्रेजेंटेशन लॉजिक क्लास का purpose, जिसे मैं short के लिए Logic कहता हूं, exactly जैसा name implies है – यह container ViewModel और backend repositories को coordinate करने के काम को handle करता है।

अगर non-stop event की notified किया जाता है, तो यह सभी coroutines को cancel कर देगा, इसमें कोई Android platform code नहीं है, जो इसे loosely coupled और बहुत easy to test बनाता है।

मैं इसे इस ऐप के desktop version के लिए reuse करने पर भी consider कर सकता हूं।

But we'll see।

ViewModel का purpose भी exactly वही करना है जो name implies – यह user interface का एक virtual representation है जिसे view observe करती है।

Simpler terms में, यह view का एक model है, यह function types expose करता है, जो observer pattern के लिए एक बहुत simple और easy standing है।

उन situations में जहां हमें multiple observers की आवश्यकता नहीं होती।

हर बार जब हमारा logic class ViewModel को update करता है, तो ViewModel automatically नया data view को publish कर देगा।

इस ViewModel के साथ एक और design decision यह है कि यह Jetpack ViewModel को extend नहीं करता।

इस decision के several reasons हैं, उनमें से कुछ simple हैं, और कुछ quite technical।

Simple reason यह है कि Jetpack ViewModel का उपयोग Android platform के साथ tight coupling create करता है।

And इसका अपना set of boilerplate code और dependencies है, जिनका मैं huge fan नहीं हूं।

In short, यह इस particular application में more problems create करता है बजाय solve करने के।

And मैं ViewModels create करने का practice करना चाहता था जो Kotlin Desktop या Kotlin/JS के लिए usable हो सकते हैं।

The technical reason क्यों है कि इस application में, हम simply data को persist करने की आवश्यकता नहीं है activity instances या process death across ताकि एक good user experience हो।

इसके बजाय, हम just Android file system को एक fairly cheap call करते हैं और data को वहां से reload करते हैं अगर ऐसी events occur होती हैं।

अब, इससे पहले कि आप उस reasoning को हर situation में apply करें, समझें कि file system से data reload करना इस application में fine काम करता है, लेकिन इसे हर application में unsaved instance state के लिए suitable replacement नहीं माना जाना चाहिए जो आप लिखते हैं।

अगर आपको models और saved state handle पसंद हैं, तो right ahead जाएं और उपयोग करें।

हम strategy pattern को भी employ करते हैं उस interface को clean up करने के लिए जिसे हमारा logic class container और view को expose करता है।

Sealed class का प्रत्येक subclass एक action को represent करता है जो view या container में occur हो सकता है।

Rather than हर UI event के लिए एक function होने के।

हमारे पास एक function है जो एक single object accept करता है जो multiple different paths of execution को represent कर सकता है।

वह है Strategy Pattern।

UI पैकेज पर right-click करें, और new package पर जाएं जिसे ActiveGame कहेंगे।

इस नए पैकेज पर right-click करें, New Kotlin File या Class पर जाएं, और हम एक interface create करने जा रहे हैं, और यह वाला ActiveGameContainer कहलाएगा।

यह word container एक technical term है।

जिस तरह से मैं इसे यहां उपयोग कर रहा हूं वह है kind of signify करना कुछ ऐसा जो एक application के large portion या पूरे application को contain करता है।

मेरी opinion में, एक container usually application के business तरह के logic के साथ ज्यादा deal नहीं करता।

यह basically just चीजों को wire together करता है और चीजें build करता है और kind of एक entry point के रूप में serve करता है।

ट्यूटोरियल के अगले भाग में, मैं explain करूंगा कि हम actually क्या container के रूप में उपयोग करने जा रहे हैं।

But एक interface का उपयोग करके यहां, मैं basically quite explicitly state कर रहा हूं कि मैं अपना mind बदल सकता हूं कि हम container के रूप में क्या उपयोग करते हैं।

Anyways, यह केवल दो abstract functions contain करता है।

ActiveGame पैकेज पर फिर से right-click करें, और हम इस बार एक sealed class create करने जा रहे हैं।

And यह वाली ActiveGameEvent कहलाएगी।

तो जैसा मैंने above comment में explain किया, ActiveGameEvent sealed class दी गई feature के हर तरह के user interaction को represent करती है, इस मामले में, active game feature।

यह एक very common pattern है जो मैं उपयोग करता हूं और हम देखेंगे कि यह हमारे BaseLogic abstract class के साथ कैसे काम करता है जिसे हमने common पैकेज में create किया था।

Okay, हम अब अपना ViewModel create करने जा रहे हैं।

Firstly, चलिए यहां एक छोटा सा class create करते हैं जो एक single tile का virtual representation होगा Sudoku puzzle में।

तो obviously, x और y particular Sudoku tile के x और y coordinates को represent करते हैं। Value वह represent करेगा जो हम graph data structures में color के रूप में बात करते थे – फिर से, यह literally just एक number है, मुझे नहीं पता क्यों हमें इसे color कहना पड़ता है।

अब hasFocus indicate करता है कि user ने एक particular tile पर click किया है, जिसके बाद वे input buttons में से एक पर click करके उस particular number को change कर सकते हैं।

And finally, एक read-only tile आप उसे एक tile के रूप में सोच सकते हैं जो puzzle में given clue की तरह है।

इसलिए, user को actually किसी read-only tiles को change करने की अनुमति नहीं है।

तो इससे पहले कि हम इस ViewModel को पढ़ना शुरू करें, मैं यहां एक couple of things mention करना चाहता था।

जैसा इस particular section के introduction में discuss किया गया, मैं actually Jetpack libraries में से किसी का उपयोग नहीं करना चाहता था publisher-subscriber relationship achieve करने के लिए ViewModel और view के बीच।

अब, यह पता चलता है कि publisher-subscriber relationship या pattern actually काफी easy है implement करने के लिए।

But इस case में, मैं actually इसे simpler पाया just Kotlin function types का उपयोग करके जो मैं call करूंगा एक poor person's publisher-subscriber pattern या observer pattern।

तो यह really means something simple in practice, हालांकि यह kind of complicated लग सकता है उनके लिए जो really familiar नहीं हैं function types के साथ काम करने में।

हमारा ViewModel इन nullable function type references को possess करेगा।

जैसा हम एक moment में देखेंगे, जो हम कर सकते हैं वह यह है कि हर बार जब हम ViewModel को presentation logic class से update करते हैं, हम तब view को by extension update कर सकते हैं इन function types को ViewModel के अंदर से invoke करके।

अब, reason क्यों हम nullables यहां उपयोग कर रहे हैं वह है ViewModel के अंदर से।

मैं never 100% certain हो सकता हूं अगर actually कुछ listening है।

But इसके साथ कहा जा रहा है, मुझे feel होता है अगर मैंने इस particular class के साथ कुछ घंटों तक खेला, तो मैं probably इसे थोड़ा streamline कर सकता हूं और maybe इनमें से कुछ internal variables को private या ऐसा कुछ बना सकता हूं।

तो really जो मैं यहां कह रहा हूं वह है – feel free to ले लें यह general idea एक ViewModel का जो tightly coupled नहीं है Android Jetpack से, लेकिन भी feel free to experiment करें इसके साथ और देखें अगर आप इसे optimize कर सकते हैं।

तो इसके out of the way होने के साथ, चलिए कुछ function types create करते हैं।

तो इन सभी function types को sub से prefix किया जाएगा good legibility ensure करने के लिए।

ActiveGameScreenState actually कुछ ऐसा है जिसे हम tutorial के next part में create करेंगे।

तो just आगे बढ़ें और इसे glowing red यहां छोड़ दें।

Okay, मुझे just briefly इन different function types को explain करने दें।

तो boardState basically Sudoku board का virtual representation है।

Obviously, contentState basically just means तीन different states।

तो either हम data load कर रहे हैं, user के पास currently active game है जिसे वे solve कर रहे हैं, या user ने एक particular game complete कर लिया है।

हम इसका उपयोग different states के बीच animate करने के लिए करेंगे user interface में।

अब, timerState का count-up timer से लेना-देना है जो basically record करता है कि user को एक given Sudoku game complete करने में कितना समय लगता है।

तो just hopefully किसी confusion को clear करने के लिए यहां – timerState actual long value होगी milliseconds में represent करने वाली time और फिर subTimerState वह तरीका है जिससे हम actually user interface को update करते हैं नए timerState को update करने के बाद।

चलिए बाकी variables को finish off करते हैं।

ये quite obviously default values हैं।

Next, हम इस ViewModel को initialize करने के लिए एक function लिखेंगे।

Okay, तो चलिए just एक moment के लिए pause करते हैं।

हम यहां क्या कर रहे हैं वह यह है कि हम data की state ले रहे हैं जैसी वह storage में exist करती थी, हम उसे ViewModel दे रहे हैं, और फिर जो हम कर रहे हैं वह है ViewModel का अपना virtual representation build करना उस state का।

अब, ViewModel के internal representations में चीजें होंगी जैसे hasFocus, जो specifically user interface की चिंता हैं और necessarily कुछ ऐसा नहीं जिसे मैं original domain model में include करूंगा।

Also, case में आप wonder कर रहे हैं, key value basically X value और Y value को hash करके create की जाती है।

यह कुछ ऐसा है जिसे हम इस tutorial में बहुत early cover कर चुके हैं, case में आप ahead jump कर गए हों।

फिर से, ActiveGameScreenState कुछ ऐसा है जिसे हम Compose भाग में create करेंगे।

यहां, हम उस data को ViewModel से bind कर रहे हैं।

And फिर हम अपने function types को invoke करेंगे view को update करने के लिए assuming यह listening है।

And वह है हमारे init function के लिए।

अब, हमारे पास just कुछ और functions हैं, जिन्हें हमारा presenter call करेगा ViewModel की state के साथ various चीजें करने के लिए।

तो यहां, हम just एक individual tile update कर रहे हैं।

तो जो हम यहां कर रहे हैं वह यह है कि जब user एक particular tile hit करता है, वह एक message भेजने वाला है presenter में, जिसमें एक particular x और y coordinate होगा, और फिर presenter इस particular function को call करेगा।

And तो वह क्या करेगा वह यह है कि वह उस tile को look करेगा जिसे user ने click किया था based on उस X और Y value पर, और उसे hasFocus = true पर set करेगा।

And फिर हर दूसरे tile के लिए, हम उसे false पर set करना चाहते हैं।

Otherwise, हम एक situation में हो सकते हैं जहां user ने multiple different tiles select कर लिए हैं, जो कुछ ऐसा नहीं है जो हमारा application supposed to be allowed to do।

And यह वह situation होगी जहां हमारे back end ने determine किया है कि current puzzle complete है।

ActiveGame पैकेज पर right-click करें।

And चलिए एक new Kotlin class create करते हैं, जो ActiveGameLogic कहलाएगी।

Okay, तो इससे पहले कि हम proceed करें, यह definitely उन situations में से एक है जहां मैं strongly suggest करता हूं complete source code को side में open रखते हुए आप यहां follow along करें।

Obviously, मैं अपनी best कोशिश करूंगा कोई mistakes न करने की, लेकिन यह possible है कि मैं mistake करूं।

ActiveGameLogic इस particular feature के application का presentation logic represent करता है।

जैसा हम देखेंगे, यह container, ViewModel, और फिर by extension, view के साथ coordinate करता है, साथ ही application के back end के साथ भी।

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

Okay, तो just a bit of review इससे पहले कि हम आगे बढ़ें – फिलहाल के लिए container actually एक Activity होगी।

But वहां एक possibility है future में, मैं fragments को containers के रूप में उपयोग करने के लिए move कर सकता हूं, इस point पर, मैं really नहीं चाहता, लेकिन हम just देखेंगे अगर वह future में sense बनाता है।

But यह entire reason है क्यों मैंने एक interface यहां include किया है ताकि मैं जो interface के पीछे है उसे very easily बदल सकूं।

ViewModel pretty clear है, हमने just उसे लिखा।

GameRepo वह है जहां हम game data store करते हैं।

तो उसमें game settings के साथ-साथ user के game की current progress शामिल है।

StatsRepo वह है जहां हम records store करते हैं shortest times to completion के लिए प्रत्येक different difficulty और size के puzzle के लिए।

And अगर आप wonder कर रहे हैं कि dispatcher क्या है तो वापस जाएं और common पैकेज देखें जब हमने उसे create किया था – हमने यह DispatcherProvider create किया था और मैंने basically उसका purpose वहां explain किया था।

BaseLogic भी कुछ ऐसा है जिसे हमने common पैकेज में create किया था।

And हम वह function देखेंगे जिसे हम उस क्लास से inherit करते हैं एक moment में।

Okay, चलिए coroutines के बारे में एक moment के लिए बात करते हैं।

तो scopes के बारे में सोचने का एक तरीका, चाहे हम coroutine scope की बात कर रहे हों, या Dagger, या whatever, वह है really just consider करना कि यह एक lifecycle के बारे में है।

अब, आप probably wonder कर रहे हैं, क्यों हम कुछ ऐसा नहीं बना रहे जैसे ViewModel या Fragment या Activity हमारी lifecycle class? Well, case में आपने अब तक notice नहीं किया, मुझे किसी भी तरह का tight coupling Android platform से पसंद नहीं अगर मैं avoid कर सकूं।

कई अन्य reasons हैं।

But मुख्य ones में से एक है कि क्योंकि यह class सारी presentation logic contain करती है, एक sense में, यह इस feature के application के लिए head decision maker है, तो, मेरी opinion में, यह sense बनाता है इसे responsible बनाने के लिए किसी भी coroutines के cancellation के लिए जो currently running होते हैं as far as इस onEvent function का सवाल है जिसे हम BaseLogic से inherit करते हैं।

Well, basically यह Strategy Pattern का एक implementation है।

मैं आपको यहां long और technical explanation नहीं दूंगा।

यह actually एक very simple pattern है।

But basically, यह sort of जैसे एक singular entry point provide करता है इस particular class में।

तो instead of हर event के लिए single function होने के, हमारे पास एक function है जो एक argument लेता है, हमारा ActiveGameEvent, जो capable है सभी different events को represent करने का, और मैं just find करता हूं कि यह really cleans up करता है interfaces को different classes के बीच, interfaces का उपयोग general sense में उस statement में।

Okay, पहले हम अपना coroutine context implement करते हैं।

याद रखें, JobTracker BaseLogic में exist करता है, लेकिन हमें इसे initialize भी करने की जरूरत है। Okay, अब इससे पहले कि हम proceed करें, कुछ really important है जिसे हमें implement करने की जरूरत है जो है एक coroutine timer।

जैसा मैंने पहले mention किया, ActiveGame Screen में एक count-up timer है।

अब आप में से कुछ probably wonder कर रहे होंगे, क्यों मैंने Java Timer class या Android के count-up timer या whatever यह कहलाता है उपयोग नहीं किया।

Basically, मैंने उन चीजों को try किया था।

And उन्होंने different application-breaking problems present किए।

And यह turned out easiest था just इस तरह का coroutine timer create करने के लिए।

Okay, तो इसको थोड़ा explanation require करता है, obviously।

तो हम यहां दो different keywords notice करेंगे जो कुछ आप में से कुछ के लिए unfamiliar हो सकते हैं – हमारे पास inline और crossinline keywords हैं।

तो जब भी आप inline keyword देखते हैं, easiest way उसे समझने का है यह समझना कि वह just means copy, paste।

And अगर आप जानना चाहते हैं कि code में उसका क्या मतलब है, तो मैं suggest करता हूं आप अपने कुछ Kotlin code को decompile करें जो inline modifier का उपयोग करता है, और आप देखेंगे कि inline function actually call site में copy और paste हो जाता है।

अब हमारे पास यहां कुछ और चल रहा है जो है crossinline function type।

तो इससे पहले कि मैं explain करूं crossinline action क्या है, चलिए बात करते हैं कि यह function actually क्या करता है।

तो यहां हमारे पास एक pretty standard spin lock है – while(true)।

तो यह एक loop है जो just endlessly execute करने वाला है, यह उस function type को invoke करेगा, और फिर 1000 milliseconds के लिए delay करेगा।

अब, यहां एक couple different चीजें चल रही हैं।

Number one, आपको समझना होगा कि हम इस coroutine को delay करने जा रहे हैं, लेकिन यह actually उस thread को block नहीं करने वाला जिस पर यह है, जो of course एक big win है।

अब दूसरी चीज जो चल रही है वह है action एक lambda expression होने वाला है जिसे हम इस particular function में pass in करेंगे।

Really केवल एक चीज जो crossinline करता है वह है यह basically makes it so कि lambda function में जिसे हम इस function में pass in करेंगे, हम allowed नहीं हैं return statement लिखने के लिए उस function में।

तो most general sense में यहां, जो हम कर रहे हैं वह है हम एक preventative step ले रहे हैं, एक situation avoid करने के लिए जहां हम accidentally return कर सकते हैं उस lambda के अंदर से जिसे हम यहां pass in करते हैं causing unexpected behavior।

अब, आप probably wonder कर रहे हैं, चूंकि हमारे पास यह endless loop चल रहा है, हम actually इस particular coroutine को कैसे stop करते हैं? Well, जो हम करने जा रहे हैं वह है हम एक Job create करने जा रहे हैं।

चलिए just वह अभी करते हैं।

And जो हम soon करेंगे वह है हम actually इस job variable को अपने startCoroutineTimer पर assign करेंगे, और वह हमें उसे cancel करने की अनुमति देगा।

चलिए इस timer business के साथ करने के लिए एक और quick extension function लिखते हैं और फिर मैं explain करूंगा कि वह क्या करता है।

User interface के साथ experiment करने में, timer को least janky या most accurate बनाने के लिए जो वह हो सकता था, यह turned out कि हर बार जब हम उसे back end में write करते हैं तो value से one subtract करना एक more consistent timer create करता था।

But एक particular edge case है अगर value zero के equal है, तो obviously हम one subtract नहीं करना चाहते उससे।

Otherwise timer negative one पर first कहेगा और वह just बहुत good नहीं लगता।

Okay, तो इसके साथ done सब होने के साथ, हम बाकी presentation logic implement करने पहुंच सकते हैं। Okay, तो जब user एक input button hit करता है, हम दो different situations हो सकती हैं।

एक situation में, user ने already एक tile select किया है, जो focus tile बन जाएगा।

या यह हो सकता है कि उन्होंने just एक input button hit किया बिना actually एक tile focus किए, जिस case में, हम really कुछ नहीं करना चाहते।

Okay, तो अगर आप gameRepo के details के बारे में wonder कर रहे हैं, तो आप tutorial के उस भाग पर वापस जा सकते हैं जहां हम actually उसे build करते हैं।

Basically, हम एक lambda create करने जा रहे हैं success case को represent करने के लिए, और फिर एक और lambda represent करने के लिए जैसे error exception case, इसे थोड़ा more legible बनाने के लिए।

मैं just यहां एक comment add कर दूंगा।

Okay, तो अगर आप फिर wonder कर रहे हैं कि हम actually timer को कैसे cancel करते हैं, यह exactly है कैसे हम करते हैं – हम job object को cancel करते हैं।

अब हम इस other function को एक moment में लिखेंगे।

Basically, अगर यह एक new record है, तो हम user interface को slightly differently render करना चाहते हैं than अगर यह record नहीं था।

But इससे पहले कि हम वह करें, चलिए error case को finish off करते हैं।

तो actually यह जानने के लिए कि यह record है या नहीं, हमें actually value को वापस StatsRepo में pass करना होगा just उस पर check करने के लिए।

तो मैं honest होने जा रहा हूं – इस application में error handling न best है, न really worst – showError well, फिलहाल just actually एक toast message दिखाएं explain करते हुए कि कुछ error occur हुआ।

Okay, just एक quick fix।

यह actually supposed to be elapsedTime है नहीं timerState।

Next, हमारे पास onNewGameClicked है।

आप यहां एक recurring theme notice करेंगे, जो यह है कि किसी भी समय हम concurrent operations perform करना चाहते हैं।

तो anytime हम back end के साथ काम कर रहे हैं, हम उसे एक launch coroutine में wrap करने जा रहे हैं।

Coroutines के साथ काम करने के बहुत सारे different ways हैं।

यह just one of the most simple straightforward ways है इसे करने का मेरी opinion में।

Okay, तो जो हम यहां कर रहे हैं वह है पहले हम ViewModel से पूछ रहे हैं कि क्या user ने current game complete कर लिया है, अगर उन्होंने नहीं किया, तो हम actually user द्वारा अपने current game में की गई progress को store करना चाहते हैं जब वे onNewGameClicked hit करते हैं, क्योंकि maybe उन्होंने इसे by accident hit किया, या वे वापस जाकर game finish करना चाहते हैं या कुछ reason जैसे कि।

That's right updateWithTime function।

फिर से, हमारे पास success और error cases हैं।

Hopefully, वह इस point पर pretty clear है।

Next, हम उस function को implement करेंगे।

Next, हम cancelStuff function लिखेंगे।

तो basically, cancelStuff function essentially हर coroutine को cancel करता है।

Next, हम onStart लिखेंगे।

मैं earlier mention करना भूल गया, reason क्यों हमने underscore रखा था functions में से एक में isComplete के लिए।

यह just kind of एक convention है एक lambda argument या parameter के लिए जो actually used नहीं होता end up में।

इस case में, हम इसे उपयोग करने जा रहे हैं।

Okay, तो obviously, यह वह जगह है जहां हम coroutine timer start करते हैं, और हम केवल यह करना चाहते हैं जब onStart call होता है।

अब फिर से, मुझे feel होता है जैसे मैं इसे थोड़ा better handle कर सकता था, यह kind of goes against my rules standard flow of the application को एक exception के रूप में consider करने के लिए।

But basically, जो हम यहां करने जा रहे हैं वह यह है कि event में जब हम storage से current game मांगते हैं, और यह actually कुछ retrieve नहीं करता, generally speaking, यह particular situation occur करने वाली है जब user ने first time application run किया है, और hence, वहां actually कोई data stored नहीं होगा।

तो उस particular case में, हम यह करना चाहेंगे।

अब, हम यहां also end up कर सकते हैं किसी kind के legitimate exception की वजह से, लेकिन उस particular case में, मैं अभी भी think करता हूं new game feature पर navigate करना actually एक good way है उसे handle करने का – good लेकिन maybe best नहीं।

Next हमारे पास onStop है।

Okay, तो onStop actually Android activity या fragment के lifecycle से tied है जिससे यह bound है।

तो जब यह function call होता है, वह basically mean करता है कि हम user की current progress save करना चाहते हैं और फिर kind of सब कुछ shut down करना चाहते हैं।

Finally, हमारे पास onTileFocus है।

तो यह होगा जब user actually एक particular Sudoku tile select करता है।

अब यह function incredibly complicated है, तो brace yourself।

Okay, I lied।

वह actually सब कुछ है जो हमें करने की जरूरत है।


ट्यूटोरियल के इस भाग में, हम active game feature के लिए user interface create करेंगे।

आगे बढ़ने से पहले मैं strongly suggest करता हूं आप मेरा video देखें जिसका title है "How to Understand Jetpack Compose: A Beginner's Guide to Composables and Recomposition"।

हम बहुत सारे composables लिखने जा रहे हैं और recomposition set up भी करेंगे।

But वह वीडियो उन लोगों के लिए बना है जो Compose के साथ just getting started हैं।

यह explain करता है कि composable क्या है, recomposition क्या है, और recomposition को wrong way में करने से कैसे avoid करें।

And believe me, recomposition को screw up करना fairly easy है अगर आप aware नहीं हैं कि यह कैसे काम करता है।

मैं इस वीडियो को नीचे pinned comment में link करूंगा।

यह वीडियो ton of information से भरा है, तो please description box में timestamps को देखें।

Topics में शामिल हैं – basic widgets जैसे Text, TextField, Button, Image, Icon, Spacer, और Divider। Layouts जैसे Box, Column, Row, BoxWithConstraints, और मेरा favorite ConstraintLayout।

Simple transition animations loading screen, active game, और complete game के बीच animate करने के लिए।

मैं आपको यह भी दिखाता हूं कि हमारे presentation logic और ViewModel के साथ function types और lambda expressions का उपयोग करके कैसे communicate करें।

Compose कोड लिखने से पहले, हालांकि, मैं आपको दिखाता हूं कि Activity को composables के लिए container के रूप में कैसे set up करें।

इसके लिए process लगभग identical है fragments के लिए अगर आप उन्हें prefer करते हैं।

मैं आपको यह भी दिखाता हूं कि एक बहुत simple dependency injection extension function कैसे लिखें, जो backend details को front end से hide करता है।

ActiveGame पैकेज पर right-click करें और new Activity पर जाएं और Empty Activity चुनें।

And सुनिश्चित करें कि आप generate layout file को uncheck करें।

And यह Activity ActiveGameActivity कहलाएगी।

यह launcher activity होगी।

अब case में आप wonder कर रहे हैं कि हम इस wizard का उपयोग क्यों कर रहे हैं instead of just एक class file create करने के।

Reason simply यह है कि wizard का उपयोग करके यह manifest में एक entry add कर देगा ताकि हमें वह न करना पड़े।

Just briefly recap करने के लिए इस activity का purpose यहां – यह एक feature-specific container के रूप में है।

चलिए अपने ActiveGameLogic class का reference create करके शुरू करते हैं।

Next, चलिए ActiveGameContainer interface implement करते हैं।

Red पर click करें और Alt+Enter hit करें।

ShowError के लिए, हम उस extension function का उपयोग करेंगे जिसे हमने way earlier इस tutorial में create किया था।

आगे बढ़ें और import करें।

Also case में आप wonder कर रहे हैं, यह single-expression syntax है यहां – यह basically just brackets और return statement को just equals sign से replace करता है।

Next one implement करें onNewGameClick।

अब obviously हमने NewGameActivity अभी तक create नहीं की है तो वह red दिखाएगा जब तक हम उसे नहीं करते।

हमें दो और lifecycle methods override करने की भी जरूरत है।

यहां logic class को बताएंगे कि सब कुछ ready to go है।

And फिर हम onStop भी override करेंगे।

And वह obviously logic class को signal करेगा कि time है things cancel करने का और tear down करने का।

Finally, हमें just onCreate में कुछ lines add करने की जरूरत है।

सबसे पहले, ViewModel create करें।

अब, यह एक really important part है – जो हम next करने जा रहे हैं वह है basically हमारे composable को anchor करना जिसे हम tutorial के next part में create करेंगे, इस activity पर यहां।

यह भी कुछ ऐसा है जिसे आप fragment के अंदर call कर सकते हैं।

आगे बढ़ें और import करें।

Naturally, हम सब कुछ अपने Compose theme में wrap करने जा रहे हैं।

अब, यह एक very critically important चीज है समझने के लिए और एक very important pattern।

तो जब हम ActiveGameScreen create करते हैं, हम एक function type pass in करने जा रहे हैं, जो हमारे event handler के रूप में serve करेगा, जो basically मेरा तरीका है यह कहने का कि वह वह तरीका है जिसमें हम events, onClick events और stuff जैसे कि, जो composable में occur होते हैं, को हमारे presentation logic class में forward करेंगे।

तो सुनिश्चित करें कि आप इस पर ध्यान देते हैं जो मैं यहां कह रहा हूं, क्योंकि यह एक really important part है, even अगर आप presenters या whatever उपयोग नहीं करते।

Function types एक excellent way हैं onClick events handle करने के लिए, especially अगर आप उन्हें Strategy Pattern के साथ combine करते हैं, जिसकी हमने previous section में चर्चा की थी।

Okay, अब, अगर आपने कभी function reference नहीं देखा, मुझे believe है इसे basically कहा जाता है जो हम यहां कर रहे हैं वह है हम onEvent function of the logic class को point कर रहे हैं।

यह really एक function reference है।

तो hopefully, आप समझ सकते हैं कि मैं यहां क्या बात कर रहा हूं।

हम ViewModel भी pass in करेंगे।

अब lastly, हमें actually अपने Logic class को build करने की जरूरत है।

तो जो हम करेंगे वह है हम उस कोड को एक extension function में लिखेंगे, लेकिन जो हम first कर सकते हैं वह है just इसे यहां read करना।

And वह है वह सब कुछ जो हमें अपनी Activity में करने की जरूरत है।

ActiveGame पर right-click करें, new package पर जाएं, और यह package BuildLogic कहलाएगा।

उस पैकेज पर right-click करें और New Kotlin File या Class पर जाएं – यह एक file होने जा रही है।

And यह वाली BuildActiveGameLogic कहलाएगी।

अगर आप मेरे channel या live streams को कुछ समय से देख रहे हैं, तो आप जानेंगे कि मैं dependency injection service locators के बारे में बहुत बात करता हूं।

And उन चीजों में से एक जो मैं all the time कहता हूं वह यह है कि एक small application में, आपको really DI container जैसे Hilt, Dagger, whatever उपयोग करने की जरूरत नहीं है, आप इसे उपयोग कर सकते हैं।

But जो मैं हमेशा beginners के लिए advise करता हूं वह है वह कोड लिखें जो ये चीजें आपके लिए generate करती हैं, खुद पहले, ताकि आप समझें कि ये frameworks आपके लिए क्या करते हैं।

तो exactly वही है जो हम करने जा रहे हैं।

हम वह kind of चीज लिखने जा रहे हैं जो ये frameworks आपके लिए generate करती हैं।

And एक small application में, यह actually very simple code है लिखने के लिए।

And, of course, यह ActiveGameLogic return करने जा रहा है।

Okay, चलिए just एक moment के लिए यहां pause करते हैं।

तो case में आप wonder कर रहे हैं कि हम storage directory का path कैसे get करते हैं जिसे हम इस application के लिए उपयोग कर सकते हैं, आप context.filesDir.path call कर सकते हैं।

Finally, हमारा dispatcher।

And वह है वह सब जो हमें करने की जरूरत है।

ActiveGame feature पर right-click करें, और new Kotlin file create करें जिसे ActiveGameScreen कहेंगे।

पहले, चलिए एक enum create करते हैं।

यह enum उन different states को represent करता है जो user interface का यह feature possess कर सकता है।

The actual state ViewModel में held है, लेकिन हम देखेंगे कि हम अपने composable UI को कैसे update कर सकते हैं ViewModel के function types को bind करके जिन्हें हमने tutorial के previous part में create किया था।

ActiveGameScreen root composable को represent करता है।

Composables के इस hierarchy में, इसकी responsibility है UI के core elements को set up करना, और उनके बीच animate करना भी।

Event handler function type reference है वह कैसे हम presentation logic को call back करते हैं।

जब user application के साथ interact करता है, यह किसी भी composable को pass down किया जाना चाहिए जिसमें ऐसे interactions हों।

हम ViewModel भी pass in करते हैं, जो है कैसे हम actually अपने UI को data देते हैं।

Very simple language में, जब भी हमारे पास किसी तरह का data या state होता है जो runtime पर change हो सकता है, हम उस data को remember delegate में wrap करना चाहते हैं।

यह Compose library को under the hood बताता है, changes के लिए watch करने के लिए और UI को redraw करने के लिए अगर कोई change occurs होता है।

अब MutableTransitionState specifically animations के लिए उपयोग किया जाता है यहां, तो इसे everywhere उपयोग न करें।

हम एक more general-purpose example of a remembered state बाद में देखेंगे।

Remember delegate Compose को updates के लिए prepare करता है, लेकिन हमें एक तरीका भी चाहिए actually value को update करने का।

हम ऐसा करते हैं एक lambda expression को ViewModel के उन function types में से एक से bind करके जिन्हें यह possess करता है।

जब उन functions में से कोई ViewModel में invoke होता है, तो program automatically jump करता है और इस code को हमारे composable के अंदर execute करता है।

यह है जो actually recomposition को trigger करता है।

हमारे पास एक remembered transition state है और उस state को ViewModel से update करने का एक तरीका।

अब हमें transition animations themselves को set up करने की जरूरत है।

यह वह जगह है जहां आप as creative as you like प्राप्त कर सकते हैं।

इस app में।

प्रत्येक content state का अपना composable associated है।

हम उनके बीच animate करते हैं simply alpha value या transparency बदलकर।

अब यह a moment ago red था, जिस तरह से मैंने उसे fix किया वह था manually Compose runtime को import करना।

तो TransitionSpec Compose को details बताता है कि animation कैसा दिखना चाहिए।

Essentially, इसका मतलब है हमें अपने खुद के mathematical instructions लिखने की जरूरत नहीं, जो great है मेरे जैसे किसी के लिए जो arithmetic में suck करता है।

Compose के लिए एक option है Scaffold composable का उपयोग करना आपके UI के skeleton के रूप में।

I personally prefer इसे खुद करने के लिए, चूंकि यह really not that difficult है, और यह मुझसे कुछ भी hide नहीं करता।

पहले, हमारे पास हमारा AppToolbar है।

चलिए आगे बढ़ते हैं और वह new game icon create करते हैं।

ये icons Compose Material library से आते हैं, मैं highly recommend करता हूं आप उसका उपयोग करें।

यह है कैसे हम actually एक onClick event trigger करते हैं।

जैसा previous part में explain किया गया था इस tutorial के, हमारे toolbar icon को यहां create करके और उसे AppToolbar composable में pass करके, हम AppToolbar को reusable बनाते हैं।

Toolbar के नीचे हमारे पास इस screen का main content है, जिसमें तीन different states हो सकते हैं।

हर बार जब recomposition occurs होता है, यह एक statement फिर से execute होगा।

The act of alpha value बदल जाएगा जब transition animation occurs होता है, इस प्रकार previous content state को fade out करके और नए को fade in करके।

Obviously, हम इन्हें एक moment में create करेंगे।

And वह है हमारे root composable के लिए।

हमारे UI का सबसे complex part एक active Sudoku game से आता है।

एक 9x9 puzzle में 81 different Text composables हैं, जो widgets की एक large number है।

जिस तरह से मैं इस composable को लिखने के बारे में गया वह यह था कि Sudoku game के प्रत्येक part को एक particular layer या element के रूप में सोचा जाए।

God composable लिखने से avoid करना सुनिश्चित करें helper functions का उपयोग करके जो UI को smallest reasonable parts में break down करते हैं।

BoxWithConstraints kind of एक composable wrapper है जो हमें height, width, और अन्य measurements के बारे में information देता है, हम उस information को इसके lambda expression के अंदर उपयोग कर सकते हैं।

हमें screen width जानने की जरूरत है ताकि हम determine कर सकें कि Sudoku board कितना wide और tall होना चाहिए।

यहां हम इस constraint layout के max width के लिए पूछते हैं।

यहां हम इस layout composable के max width के लिए पूछते हैं।

But हमें उस value को density-independent pixels में चाहिए, और उसे current screen के density के relative भी होना चाहिए।

वहां .dp extension function आता है।

And यह local density का उपयोग करके उस value को determine करता है।

Board का margin भी screen density के based पर change होना चाहिए।

मैं इन values पर arrived हूं simply app को various densities पर emulator का उपयोग करके test करके।

Next, हम एक ConstraintLayout लिखेंगे, जो dynamic layouts manage करने का एक totally awesome तरीका है।

अब composables को एक दूसरे को constrain करने के लिए, हमें एक तरीका चाहिए उनके लिए एक दूसरे को reference करने का।

यह XML views के लिए IDs set करने के equivalent है।

पहले, हम ये references create करते हैं और आप देखेंगे कि उन्हें बाद में कैसे bind करना है।

चलिए puzzle board के लिए एक layout container create करते हैं।

Okay, तो यह really important देखें कैसे हम उस reference को constrainAs parameter में pass in कर रहे हैं वहां।

यह है कैसे हम actually एक particular composable को एक particular reference के साथ associate करते हैं।

यह Box composable name "board" के साथ associated होगा।

Next हम Sudoku board itself create करेंगे।

फिर से, boundary puzzle के size की तरह है।

तो यह या तो 4x4 puzzle है या 9x9 puzzle।

So boundary या तो 4 या 9 होगी।

यह supposed to say size है।

तो offset यहां used है screen real estate को evenly distribute करने के लिए प्रत्येक Sudoku tile और grid line के लिए।

यहां एक तरीका है mutable state create करने का जो किसी तरह के transition animation के साथ associated नहीं है।

तो यह more general-purpose approach है।

तो पहला argument यहां viewModel.boardState को initial value के रूप में सोचा जा सकता है, neverEqualPolicy ensures करता है कि state में minor changes जैसे hasFocus actually recomposition trigger करते हैं।

फिर से, यह है कैसे हम actually value को update करते हैं एक बार ViewModel update हो जाता है।

जैसा आप देख सकते हैं, यहां फिर से, मैं बहुत सारे helper functions का उपयोग कर रहा हूं चीजों को break down करने के लिए।

यहां हम text fields render करते हैं जो puzzle में tiles को represent करते हैं, वे या तो read-only हो सकते हैं या mutable, इस प्रकार meaning कि हमें उन्हें slightly differently render करने की जरूरत है।

तो यहां, हम कह रहे हैं अगर user एक particular tile को value zero पर set करता है, हम actually just उसे empty tile के रूप में render करना चाहते हैं।

The main idea यहां है कि हम प्रत्येक individual tile के x और y values के साथ offset का उपयोग कर रहे हैं ताकि प्रत्येक tile को evenly position किया जा सके।

तो जब user एक tile select करता है, वह focused होने वाला है और हम उस tile को obviously थोड़ा different render करना चाहते हैं एक unfocused tile से।

अब हम read-only squares render करेंगे।

Next हम board grid create करेंगे।

तो Sqrt एक extension है, जो actually computation logic में defined है।

In retrospect, मैंने probably उसे common पैकेज में define करना चाहिए था, लेकिन यह pretty obvious है कि वह क्या करता है।

तो यह function यहां, हम उन grid lines को draw करेंगे जो Sudoku puzzles को separate करती हैं।

इसे user के लिए more obvious बनाने के लिए कौन से sub-grids कौन से हैं, हम different borders draw करते हैं 4x4 या 9x9 sub-grids को separate करने के लिए।

यही कारण है कि हम यहां modulo का उपयोग कर रहे हैं।

तो यह दोनों vertical और horizontal lines draw करेगा।

Okay, तो हम GameContent composable में वापस jump कर रहे हैं उसे finish off करने के लिए।

हमारे Sudoku board के नीचे, हमारे पास कुछ different icons हैं puzzle की difficulty indicate करने के लिए।

Next हमें count-up timer के लिए एक layout container चाहिए।

यह great है, चलिए timer Text composable create करते हैं।

अब default value, यह just empty है।

Okay, हम GameContent composable में वापस हैं।

The last thing जो हमें करने की जरूरत है वह है input buttons के लिए एक layout container add करना।

अब हम यहां कुछ values hardcode करने वाले हैं और वह kind of bad practice है।

But reason यह है कि Compose team ने FlowRow को deprecate कर दिया, जिससे मैं अभी भी upset हूं, और वह perfectly इस situation के लिए work करता था, और मैं flow खुद implement करने के लिए too lazy रहा हूं।

Hey, कम से कम मैं honest हूं।

Case में आप wonder कर रहे हैं, 0.4 और 5..9 एक range emit करेंगे inclusive उन values के।

चलिए वह composable create करते हैं – Spacer pretty self-explanatory है, यह just layout में कुछ space लेता है।

Next हमारे पास buttons themselves हैं।

यह TextButton wrapper हमें एक nice looking button style करने की अनुमति देता है instead of just onClick पर एक Text composable add करने के।

Alright, वह है GameContent के लिए।

अब हमें GameCompleteContent screen करने की जरूरत है, जो obviously तब है जब user एक game finish करता है।

तो यह basically just दो images हैं जो एक दूसरे के ऊपर stacked हैं, लेकिन हम केवल उनमें से एक को render करने जा रहे हैं अगर यह actually एक new record है जो user ने बनाया।

तो चूंकि हम actually emoji events icon create नहीं करते, हम उसका color change कर सकते हैं इस color filter thing का उपयोग करके।

Pretty handy।

Next, हमारे पास दो Text composables हैं।

And वह है।

Congratulations।

  1. एंड्रॉयड और iOS के लिए यूट्यूब से शॉर्ट्स कैसे बनाएं एंड्रॉयड और iOS के लिए यूट्यूब से शॉर्ट्स कैसे बनाएं

    टिकटॉक की बढ़ती लोकप्रियता ने यूट्यूब जैसे प्रतिस्पर्धी ऐप्स को समान विकल्प जोड़ने के लिए प्रेरित किया है। नतीजतन, एंड्रॉयड और iOS पर, अब आप शॉर्ट्स कहे जाने वाले पोर्ट्रेट ओरिएंटेशन में छोटे वीडियो बना सकते हैं, जो 60 सेकंड तक सीमित हैं। इस ट्यूटोरियल में, हम आपको एंड्रॉयड और iOS ऐप्स पर यूट्यूब शॉ

  1. इन 3 फ्री एंड्रॉइड ऐप्स से आसानी से एनिमेटेड टेक्स्ट बनाएं इन 3 फ्री एंड्रॉइड ऐप्स से आसानी से एनिमेटेड टेक्स्ट बनाएं

    एनिमेटेड टेक्स्ट आपके स्टैंडर्ड टेक्स्ट की तुलना में देखने में हमेशा अधिक मजेदार होता है। जब टेक्स्ट एनिमेटेड होता है तो आप जो प्रभाव जोड़ सकते हैं वह मैसेज को बहुत अधिक आकर्षक बना देता है। अच्छी खबर यह है कि ऐसे ऐप्स हैं जो आपकी रचना में मदद कर सकते हैं। सूची में निम्नलिखित ऐप्स मुफ्त हैं, इसलिए य

  1. React Native और Native Base के साथ एक मजबूत Android न्यूज़ ऐप बनाएं React Native और Native Base के साथ एक मजबूत Android न्यूज़ ऐप बनाएं

    हम एक ऐसी दुनिया में रहते हैं जहां चीजें लगातार बदल रही हैं। इसलिए अगर आप घटनाओं से अपडेट रहना चाहते हैं, तो आपको एक अच्छा न्यूज़ ऐप चाहिए।कुछ अच्छी तकनीक सीखने और अपडेट रहने में आपकी मदद करने के लिए, इस ब्लॉग पोस्ट में हम React Native का उपयोग करके Android के लिए एक न्यूज़ एप्लिकेशन बनाएंगे। यह विभ