1. परिचय
Flutter, Google का यूज़र इंटरफ़ेस (यूआई) टूलकिट है. इसकी मदद से, मोबाइल, वेब, और डेस्कटॉप के लिए एक ही कोडबेस से ऐप्लिकेशन बनाए जा सकते हैं. इस कोडलैब में, आपको यह Flutter ऐप्लिकेशन बनाना होगा:
यह ऐप्लिकेशन, सुनने में अच्छे लगने वाले नाम जनरेट करता है. जैसे, "newstay", "lightstream", "mainbrake" या "graypine". उपयोगकर्ता, अगले नाम के बारे में पूछ सकता है, मौजूदा नाम को पसंदीदा के तौर पर मार्क कर सकता है, और पसंदीदा नामों की सूची को किसी दूसरे पेज पर देख सकता है. ऐप्लिकेशन, अलग-अलग स्क्रीन साइज़ के हिसाब से काम करता है.
आपको क्या सीखने को मिलेगा
- Flutter के काम करने के तरीके के बारे में बुनियादी जानकारी
- Flutter में लेआउट बनाना
- उपयोगकर्ता के इंटरैक्शन (जैसे, बटन दबाना) को ऐप्लिकेशन के व्यवहार से कनेक्ट करना
- अपने Flutter कोड को व्यवस्थित रखना
- अपने ऐप्लिकेशन को अलग-अलग स्क्रीन के हिसाब से रिस्पॉन्सिव बनाना
- अपने ऐप्लिकेशन का रंग-रूप और स्टाइल एक जैसा बनाए रखना
हम बुनियादी स्ट्रक्चर से शुरुआत करेंगे, ताकि आप सीधे दिलचस्प हिस्सों पर जा सकें.
यहां फ़िलिप, आपको पूरे कोडलैब के बारे में बता रहे हैं!
लैब शुरू करने के लिए, 'आगे बढ़ें' पर क्लिक करें.
2. Flutter एनवायरमेंट सेट अप करना
संपादक
इस कोडलैब को जितना हो सके उतना आसान बनाने के लिए, हम यह मानकर चल रहे हैं कि आप Visual Studio Code (VS Code) का इस्तेमाल डेवलपमेंट एनवायरमेंट के तौर पर करेंगे. यह सुविधा मुफ़्त है और सभी मुख्य प्लैटफ़ॉर्म पर काम करती है.
ज़रूरी नहीं कि आप Android Studio का ही इस्तेमाल करें. आप अपनी पसंद के किसी भी एडिटर का इस्तेमाल कर सकते हैं. जैसे, Android Studio, IntelliJ IDEs, Emacs, Vim या Notepad++. ये सभी एडिटर, Flutter के साथ काम करते हैं.
हमारा सुझाव है कि इस कोडलैब के लिए VS Code का इस्तेमाल करें. ऐसा इसलिए, क्योंकि निर्देश डिफ़ॉल्ट रूप से VS Code के खास शॉर्टकट के लिए होते हैं. "X करने के लिए, अपने एडिटर में ज़रूरी कार्रवाई करें" जैसे निर्देश देने के बजाय, "यहां क्लिक करें" या "यह बटन दबाएं" जैसे निर्देश देना ज़्यादा आसान होता है.
कोई डेवलपमेंट टारगेट चुनना
Flutter एक मल्टी-प्लैटफ़ॉर्म टूलकिट है. आपका ऐप्लिकेशन इनमें से किसी भी ऑपरेटिंग सिस्टम पर काम कर सकता है:
- iOS
- Android
- Windows
- macOS
- Linux
- वेब
हालांकि, आम तौर पर एक ही ऑपरेटिंग सिस्टम चुना जाता है, जिसके लिए मुख्य तौर पर डेवलपमेंट किया जाता है. इसे "डेवलपमेंट टारगेट" कहा जाता है. यह वह ऑपरेटिंग सिस्टम होता है जिस पर आपका ऐप्लिकेशन डेवलपमेंट के दौरान चलता है.
उदाहरण के लिए, मान लें कि आपको Windows लैपटॉप पर Flutter ऐप्लिकेशन डेवलप करना है. अगर आपने डेवलपमेंट के लिए Android को टारगेट प्लैटफ़ॉर्म के तौर पर चुना है, तो आम तौर पर आपको Android डिवाइस को Windows लैपटॉप से यूएसबी केबल की मदद से कनेक्ट करना होगा. इसके बाद, डेवलपमेंट के दौरान आपका ऐप्लिकेशन, कनेक्ट किए गए Android डिवाइस पर चलेगा. हालांकि, डेवलपमेंट टारगेट के तौर पर Windows को भी चुना जा सकता है. इसका मतलब है कि डेवलपमेंट के दौरान आपका ऐप्लिकेशन, Windows ऐप्लिकेशन के तौर पर आपके एडिटर के साथ चलता है.
वेब को डेवलपमेंट टारगेट के तौर पर चुनने का विकल्प आकर्षक लग सकता है. इस विकल्प को चुनने का नुकसान यह है कि आपको Flutter की सबसे काम की डेवलपमेंट सुविधाओं में से एक, स्टेटफ़ुल हॉट रिलोड की सुविधा नहीं मिलती. Flutter, वेब ऐप्लिकेशन को हॉट-रीलोड नहीं कर सकता.
अभी अपनी पसंद का विकल्प चुनें. याद रखें: आपके पास बाद में, अपने ऐप्लिकेशन को अन्य ऑपरेटिंग सिस्टम पर चलाने का विकल्प हमेशा होता है. हालांकि, डेवलपमेंट का टारगेट तय होने से, अगला चरण आसानी से पूरा किया जा सकता है.
Flutter इंस्टॉल करना
Flutter SDK टूल को इंस्टॉल करने के बारे में सबसे नए निर्देश, हमेशा docs.flutter.dev पर उपलब्ध होते हैं.
Flutter की वेबसाइट पर दिए गए निर्देशों में, न सिर्फ़ एसडीके को इंस्टॉल करने के बारे में बताया गया है, बल्कि डेवलपमेंट टारगेट से जुड़े टूल और एडिटर प्लगिन के बारे में भी बताया गया है. ध्यान रखें कि इस कोडलैब के लिए, आपको सिर्फ़ ये चीज़ें इंस्टॉल करनी होंगी:
- Flutter SDK
- Flutter प्लगिन के साथ Visual Studio Code
- आपके चुने गए डेवलपमेंट टारगेट के लिए ज़रूरी सॉफ़्टवेयर. उदाहरण के लिए: Windows को टारगेट करने के लिए Visual Studio या macOS को टारगेट करने के लिए Xcode
अगले सेक्शन में, अपना पहला Flutter प्रोजेक्ट बनाया जा सकता है.
अगर आपको अब तक समस्याएं आ रही हैं, तो समस्या हल करने के लिए, आपको StackOverflow पर मौजूद इन सवालों और जवाबों से मदद मिल सकती है.
अक्सर पूछे जाने वाले सवाल
- मुझे Flutter SDK का पाथ कैसे मिलेगा?
- अगर Flutter कमांड नहीं मिलती है, तो मुझे क्या करना चाहिए?
- मैं "Waiting for another flutter command to release the startup lock" समस्या को कैसे ठीक करूं?
- मैं Flutter को यह कैसे बताऊं कि मेरा Android SDK कहां इंस्टॉल है?
flutter doctor --android-licenses
चलाते समय, Java से जुड़ी गड़बड़ी को कैसे ठीक किया जा सकता है?- मुझे Android
sdkmanager
टूल नहीं मिल रहा है. मैं इस समस्या को कैसे ठीक करूं? - मैं "
cmdline-tools
कॉम्पोनेंट मौजूद नहीं है" गड़बड़ी को कैसे ठीक करूं? - मैं Apple Silicon (M1) पर CocoaPods कैसे चलाऊं?
- मैं VS Code में सेव करने पर अपने-आप फ़ॉर्मैट होने की सुविधा को कैसे बंद करूं?
3. प्रोजेक्ट बनाना
अपना पहला Flutter प्रोजेक्ट बनाना
Visual Studio Code लॉन्च करें और कमांड पैलेट खोलें (F1
या Ctrl+Shift+P
या Shift+Cmd+P
की मदद से). "flutter new" टाइप करना शुरू करें. Flutter: New Project कमांड चुनें.
इसके बाद, ऐप्लिकेशन चुनें. इसके बाद, वह फ़ोल्डर चुनें जिसमें आपको अपना प्रोजेक्ट बनाना है. यह आपकी होम डायरेक्ट्री या C:\src\
जैसा कुछ हो सकता है.
आखिर में, अपने प्रोजेक्ट का नाम डालें. जैसे, namer_app
या my_awesome_namer
.
अब Flutter, आपका प्रोजेक्ट फ़ोल्डर बनाता है और VS Code उसे खोलता है.
अब ऐप्लिकेशन के बेसिक स्ट्रक्चर के साथ, तीन फ़ाइलों के कॉन्टेंट को बदला जाएगा.
ऐप्लिकेशन को कॉपी करके चिपकाना
VS Code के बाएं पैनल में, पक्का करें कि Explorer चुना गया हो. इसके बाद, pubspec.yaml
फ़ाइल खोलें.
इस फ़ाइल के कॉन्टेंट की जगह यह कॉन्टेंट डालें:
pubspec.yaml
name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
pubspec.yaml
फ़ाइल में आपके ऐप्लिकेशन के बारे में बुनियादी जानकारी होती है. जैसे, उसका मौजूदा वर्शन, उसकी डिपेंडेंसी, और वे ऐसेट जिनके साथ उसे शिप किया जाएगा.
इसके बाद, प्रोजेक्ट में मौजूद दूसरी कॉन्फ़िगरेशन फ़ाइल analysis_options.yaml
खोलें.
इसके कॉन्टेंट को इससे बदलें:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_const_constructors_in_immutables: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_final_fields: false
unnecessary_breaks: true
use_key_in_widget_constructors: false
यह फ़ाइल तय करती है कि आपके कोड का विश्लेषण करते समय, Flutter को कितना सख्त होना चाहिए. Flutter का पहली बार इस्तेमाल करने की वजह से, ऐनलाइज़र को यह निर्देश दिया जा रहा है कि वह इस कोड को आसानी से प्रोसेस करे. इस सेटिंग को बाद में कभी भी बदला जा सकता है. असल में, प्रोडक्शन ऐप्लिकेशन को पब्लिश करने के दौरान, आपको इस ऐप्लिकेशन के लिए, विश्लेषण करने वाले टूल को और ज़्यादा सख्त बनाना होगा.
आखिर में, lib/
डायरेक्ट्री में मौजूद main.dart
फ़ाइल खोलें.
इस फ़ाइल के कॉन्टेंट की जगह यह कॉन्टेंट डालें:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [Text('A random idea:'), Text(appState.current.asLowerCase)],
),
);
}
}
अब तक, ऐप्लिकेशन में कोड की ये 50 लाइनें हैं.
अगले सेक्शन में, ऐप्लिकेशन को डीबग मोड में चलाएं और डेवलपमेंट शुरू करें.
4. बटन जोड़ना
इस चरण में, अगला बटन जोड़ा जाता है, ताकि शब्दों की नई जोड़ी जनरेट की जा सके.
ऐप्लिकेशन लॉन्च करना
सबसे पहले, lib/main.dart
खोलें और पक्का करें कि आपने टारगेट डिवाइस चुना हो. VS Code के सबसे नीचे दाएं कोने में, आपको एक बटन दिखेगा. इस बटन पर, मौजूदा टारगेट डिवाइस दिखता है. इसे बदलने के लिए क्लिक करें.
lib/main.dart
खुला होने पर, VS Code की विंडो के सबसे ऊपर दाएं कोने में मौजूद "चलाएं" बटन ढूंढें और उस पर क्लिक करें.
एक मिनट बाद, आपका ऐप्लिकेशन डीबग मोड में लॉन्च हो जाएगा. फ़िलहाल, यह बहुत ज़्यादा नहीं है:
पहली बार हॉट रीलोड
lib/main.dart
के सबसे नीचे, पहले Text
ऑब्जेक्ट में स्ट्रिंग जोड़ें. इसके बाद, फ़ाइल को Ctrl+S
या Cmd+S
के साथ सेव करें. उदाहरण के लिए:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
ध्यान दें कि ऐप्लिकेशन तुरंत बदल जाता है, लेकिन रैंडम शब्द वही रहता है. यह Flutter की मशहूर स्टेटफ़ुल हॉट रिलोड सुविधा है. सोर्स फ़ाइल में बदलाव सेव करने पर, हॉट रिलोड ट्रिगर होता है.
अक्सर पूछे जाने वाले सवाल
- अगर VSCode में हॉट रिलोड काम नहीं करता है, तो क्या होगा?
- क्या मुझे VSCode में हॉट रिलोड के लिए ‘r' दबाना होगा?
- क्या वेब पर हॉट रिलोड की सुविधा काम करती है?
- मैं "डीबग करें" बैनर को कैसे हटाऊं?
बटन जोड़ना
इसके बाद, Column
के सबसे नीचे, दूसरे Text
इंस्टेंस के ठीक नीचे एक बटन जोड़ें.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
बदलाव सेव करने पर, ऐप्लिकेशन फिर से अपडेट होता है: एक बटन दिखता है. इस पर क्लिक करने पर, VS Code में मौजूद Debug Console में button pressed! मैसेज दिखता है.
पांच मिनट में Flutter का क्रैश कोर्स
डीबग कंसोल को देखना जितना मज़ेदार है, उतना ही ज़रूरी है कि बटन कुछ ज़्यादा काम का हो. हालांकि, इससे पहले lib/main.dart
में दिए गए कोड को ध्यान से देखें, ताकि आपको यह समझ आ सके कि यह कैसे काम करता है.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
फ़ाइल में सबसे ऊपर, आपको main()
फ़ंक्शन दिखेगा. मौजूदा फ़ॉर्म में, यह सिर्फ़ Flutter को MyApp
में तय किए गए ऐप्लिकेशन को चलाने के लिए कहता है.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
MyApp
क्लास एक्सटेंड होती है StatelessWidget
. विजेट ऐसे एलिमेंट होते हैं जिनकी मदद से हर Flutter ऐप्लिकेशन बनाया जाता है. जैसा कि आप देख सकते हैं, ऐप्लिकेशन भी एक विजेट है.
MyApp
में मौजूद कोड, पूरे ऐप्लिकेशन को सेट अप करता है. यह ऐप्लिकेशन-वाइड स्टेट बनाता है (इसके बारे में बाद में ज़्यादा जानकारी दी गई है), ऐप्लिकेशन का नाम रखता है, विज़ुअल थीम तय करता है, और "होम" विजेट सेट करता है. यह आपके ऐप्लिकेशन का शुरुआती पॉइंट होता है.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
इसके बाद, MyAppState
क्लास, ऐप्लिकेशन की...ठीक है...स्टेट तय करती है. यह Flutter में आपका पहला अनुभव है. इसलिए, इस कोडलैब में आपको आसान और ज़रूरी जानकारी मिलेगी. Flutter में ऐप्लिकेशन की स्थिति को मैनेज करने के कई तरीके हैं. ChangeNotifier
को समझना सबसे आसान है. इस ऐप्लिकेशन में इसी तरीके का इस्तेमाल किया गया है.
MyAppState
से पता चलता है कि ऐप्लिकेशन को काम करने के लिए किस तरह के डेटा की ज़रूरत है. फ़िलहाल, इसमें सिर्फ़ एक वैरिएबल है, जिसमें मौजूदा रैंडम शब्द का जोड़ा है. इसमें बाद में और जानकारी जोड़ी जा सकती है.- स्टेट क्लास,
ChangeNotifier
को बढ़ाती है. इसका मतलब है कि यह अपने बदलावों के बारे में दूसरों को सूचना दे सकती है. उदाहरण के लिए, अगर मौजूदा शब्द युग्म बदलता है, तो ऐप्लिकेशन में मौजूद कुछ विजेट को इसकी जानकारी होनी चाहिए. ChangeNotifierProvider
का इस्तेमाल करके, पूरे ऐप्लिकेशन के लिए स्टेट बनाई जाती है और उसे उपलब्ध कराया जाता है (ऊपरMyApp
में कोड देखें). इससे ऐप्लिकेशन में मौजूद कोई भी विजेट, स्टेट को ऐक्सेस कर सकता है.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
आखिर में, MyHomePage
है. यह वह विजेट है जिसमें आपने पहले ही बदलाव कर दिया है. यहां दी गई हर नंबर वाली लाइन, ऊपर दिए गए कोड में मौजूद लाइन-नंबर वाले कमेंट से मैप होती है:
- हर विजेट,
build()
तरीके को तय करता है. विजेट की स्थितियों में बदलाव होने पर, यह तरीका अपने-आप कॉल होता है, ताकि विजेट हमेशा अप-टू-डेट रहे. MyHomePage
,watch
तरीके का इस्तेमाल करके, ऐप्लिकेशन की मौजूदा स्थिति में हुए बदलावों को ट्रैक करता है.- हर
build
तरीके को एक विजेट या (ज़्यादातर) विजेट का नेस्ट किया गया ट्री दिखाना चाहिए. इस मामले में, टॉप-लेवल विजेटScaffold
है. इस कोडलैब में, आपकोScaffold
के साथ काम नहीं करना है. हालांकि, यह एक काम का विजेट है और यह असल दुनिया के ज़्यादातर Flutter ऐप्लिकेशन में मौजूद होता है. Column
, Flutter में सबसे बुनियादी लेआउट विजेट में से एक है. यह किसी भी संख्या में बच्चों को लेता है और उन्हें ऊपर से नीचे तक एक कॉलम में रखता है. डिफ़ॉल्ट रूप से, कॉलम अपने चाइल्ड विजेट को सबसे ऊपर दिखाता है. जल्द ही, इसे इस तरह से बदलें कि कॉलम बीच में आ जाए.- आपने पहले चरण में इस
Text
विजेट में बदलाव किया था. - यह दूसरा
Text
विजेट,appState
लेता है और उस क्लास के सिर्फ़ एक सदस्य,current
(जो किWordPair
है) को ऐक्सेस करता है.WordPair
कई मददगार गेटर उपलब्ध कराता है, जैसे किasPascalCase
याasSnakeCase
. यहां हमनेasLowerCase
का इस्तेमाल किया है. हालांकि, अगर आपको कोई दूसरा विकल्प पसंद है, तो अब इसे बदला जा सकता है. - ध्यान दें कि Flutter कोड में, ट्रेलिंग कॉमा का ज़्यादा इस्तेमाल किया जाता है. इस कॉमा की ज़रूरत नहीं है, क्योंकि
children
इसColumn
पैरामीटर लिस्ट का आखिरी (और सिर्फ़) सदस्य है. हालांकि, आखिर में कॉमा का इस्तेमाल करना आम तौर पर एक अच्छा तरीका है: इससे ज़्यादा सदस्यों को आसानी से जोड़ा जा सकता है. साथ ही, यह Dart के ऑटो-फ़ॉर्मेटर के लिए एक हिंट के तौर पर भी काम करता है, ताकि वह वहां एक नई लाइन डाल सके. ज़्यादा जानकारी के लिए, कोड फ़ॉर्मैट करना लेख पढ़ें.
इसके बाद, बटन को स्थिति से कनेक्ट करें.
आपका पहला व्यवहार
स्क्रोल करके MyAppState
पर जाएं और getNext
का तरीका जोड़ें.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
नए getNext()
तरीके से, current
को नए रैंडम WordPair
के साथ फिर से असाइन किया जाता है. यह notifyListeners()
को भी कॉल करता है. यह ChangeNotifier)
का एक तरीका है, जिससे यह पक्का किया जाता है कि MyAppState
देखने वाले हर व्यक्ति को सूचना मिले.
अब सिर्फ़ बटन के कॉलबैक से getNext
वाले तरीके को कॉल करना बाकी है.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
सेव करें और ऐप्लिकेशन को अभी आज़माएं. अगला बटन दबाने पर, इसे हर बार शब्दों का एक नया रैंडम पेयर जनरेट करना चाहिए.
अगले सेक्शन में, यूज़र इंटरफ़ेस को और बेहतर बनाया जाएगा.
5. ऐप्लिकेशन को ज़्यादा आकर्षक बनाना
फ़िलहाल, ऐप्लिकेशन इस तरह दिखता है.
कुछ खास नहीं. ऐप्लिकेशन का मुख्य हिस्सा, यानी कि शब्दों का रैंडम तरीके से जनरेट किया गया पेयर ज़्यादा दिखना चाहिए. आखिरकार, यही मुख्य वजह है कि हमारे उपयोगकर्ता इस ऐप्लिकेशन का इस्तेमाल कर रहे हैं! इसके अलावा, ऐप्लिकेशन का कॉन्टेंट अजीब तरीके से बीच में नहीं है. साथ ही, पूरा ऐप्लिकेशन काले और सफ़ेद रंग का है, जो देखने में अच्छा नहीं लगता.
इस सेक्शन में, ऐप्लिकेशन के डिज़ाइन पर काम करके इन समस्याओं को हल किया जाता है. इस सेक्शन का मुख्य मकसद कुछ ऐसा है:
विजेट को एक्सट्रैक्ट करना
मौजूदा शब्द के जोड़े को दिखाने वाली लाइन अब ऐसी दिखती है: Text(appState.current.asLowerCase)
. इसे ज़्यादा मुश्किल बनाने के लिए, इस लाइन को अलग विजेट में एक्सट्रैक्ट करना बेहतर होता है. Flutter में, यूज़र इंटरफ़ेस (यूआई) के अलग-अलग लॉजिकल हिस्सों के लिए अलग-अलग विजेट का इस्तेमाल करना, जटिलता को मैनेज करने का एक अहम तरीका है.
Flutter, विजेट निकालने के लिए रिफ़ैक्टरिंग हेल्पर उपलब्ध कराता है. हालांकि, इसका इस्तेमाल करने से पहले पक्का करें कि निकाली जा रही लाइन सिर्फ़ ज़रूरी चीज़ों को ऐक्सेस करती हो. फ़िलहाल, लाइन appState
को ऐक्सेस करती है. हालांकि, इसे सिर्फ़ यह जानने की ज़रूरत है कि मौजूदा शब्द का जोड़ा क्या है.
इस वजह से, MyHomePage
विजेट को इस तरह से फिर से लिखें:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
बढ़िया। Text
विजेट अब पूरे appState
को नहीं दिखाता.
अब, Refactor मेन्यू खोलें. VS Code में, ऐसा इनमें से किसी एक तरीके से किया जा सकता है:
- आपको जिस कोड को फिर से फ़ैक्टर करना है उस पर राइट क्लिक करें (इस मामले में
Text
) और ड्रॉप-डाउन मेन्यू से फिर से फ़ैक्टर करें... चुनें,
या
- अपने कर्सर को उस कोड पर ले जाएं जिसे आपको रीफ़ैक्टर करना है (इस मामले में
Text
), औरCtrl+.
(Win/Linux) याCmd+.
(Mac) दबाएं.
Refactor मेन्यू में जाकर, Extract Widget को चुनें. कोई नाम असाइन करें, जैसे कि BigCard. इसके बाद, Enter
पर क्लिक करें.
इससे मौजूदा फ़ाइल के आखिर में, BigCard
नाम की एक नई क्लास अपने-आप बन जाती है. क्लास कुछ इस तरह दिखती है:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
ध्यान दें कि रीफ़ैक्टरिंग के दौरान भी ऐप्लिकेशन काम करता रहता है.
कार्ड जोड़ें
अब इस नए विजेट को यूज़र इंटरफ़ेस (यूआई) का अहम हिस्सा बनाने का समय आ गया है. हमने इस सेक्शन की शुरुआत में ऐसा करने का प्लान बनाया था.
BigCard
क्लास और उसमें मौजूद build()
तरीका ढूंढें. पहले की तरह, Text
विजेट पर Refactor मेन्यू खोलें. हालांकि, इस बार आपको विजेट को नहीं निकालना है.
इसके बजाय, पैडिंग के साथ रैप करें को चुनें. इससे Text
विजेट के चारों ओर एक नया पैरंट विजेट बन जाता है, जिसे Padding
कहा जाता है. सेव करने के बाद, आपको दिखेगा कि रैंडम शब्द के बीच में पहले से ज़्यादा जगह है.
पैडिंग को 8.0
की डिफ़ॉल्ट वैल्यू से बढ़ाएं. उदाहरण के लिए, ज़्यादा पैडिंग के लिए 20
का इस्तेमाल करें.
इसके बाद, एक लेवल ऊपर जाएं. अपने कर्सर को Padding
विजेट पर रखें. इसके बाद, Refactor मेन्यू को ऊपर की ओर खींचें और Wrap with widget... को चुनें.
इससे पैरंट विजेट के बारे में जानकारी दी जा सकती है. "कार्ड" टाइप करें और Enter दबाएं.
यह Padding
विजेट और इसलिए Text
को Card
विजेट के साथ रैप करता है.
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
}
// ...
अब ऐप्लिकेशन कुछ ऐसा दिखेगा:
थीम और स्टाइल
कार्ड को ज़्यादा आकर्षक बनाने के लिए, उसे गहरे रंग से पेंट करें. हमेशा एक जैसी कलर स्कीम का इस्तेमाल करना बेहतर होता है. इसलिए, रंग चुनने के लिए ऐप्लिकेशन के Theme
का इस्तेमाल करें.
BigCard
के build()
तरीके में ये बदलाव करें.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
इन दो नई लाइनों से कई काम किए जा सकते हैं:
- सबसे पहले, कोड
Theme.of(context)
की मदद से ऐप्लिकेशन की मौजूदा थीम का अनुरोध करता है. - इसके बाद, कोड कार्ड के रंग को थीम की
colorScheme
प्रॉपर्टी के रंग के जैसा सेट करता है. कलर स्कीम में कई रंग शामिल हैं. इनमें सेprimary
सबसे प्रमुख रंग है. यह ऐप्लिकेशन का मुख्य रंग है.
अब कार्ड को ऐप्लिकेशन के मुख्य रंग से पेंट किया गया है:
इस रंग को बदला जा सकता है. साथ ही, पूरे ऐप्लिकेशन की कलर स्कीम को भी बदला जा सकता है. इसके लिए, MyApp
तक ऊपर की ओर स्क्रोल करें. इसके बाद, ColorScheme
के लिए सीड कलर बदलें.
ध्यान दें कि रंग कितने स्मूद तरीके से ऐनिमेट हो रहा है. इसे इंप्लिसिट ऐनिमेशन कहा जाता है. कई फ़्लटर विजेट, वैल्यू के बीच आसानी से इंटरपोलेट करते हैं, ताकि यूज़र इंटरफ़ेस (यूआई) सिर्फ़ स्थितियों के बीच "जंप" न करे.
कार्ड के नीचे मौजूद एलिवेटेड बटन का रंग भी बदल जाता है. ऐप्लिकेशन के लिए Theme
का इस्तेमाल करने से, वैल्यू को हार्ड-कोड करने के मुकाबले ज़्यादा फ़ायदा मिलता है.
TextTheme
कार्ड में अब भी समस्या है: टेक्स्ट बहुत छोटा है और उसका रंग ऐसा है कि उसे पढ़ना मुश्किल है. इस समस्या को ठीक करने के लिए, BigCard
के build()
तरीके में ये बदलाव करें.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
इस बदलाव की वजह:
theme.textTheme,
का इस्तेमाल करके, ऐप्लिकेशन की फ़ॉन्ट थीम को ऐक्सेस किया जा सकता है. इस क्लास मेंbodyMedium
(मीडियम साइज़ के स्टैंडर्ड टेक्स्ट के लिए),caption
(इमेज के कैप्शन के लिए) याheadlineLarge
(बड़े हेडलाइन के लिए) जैसे सदस्य शामिल हैं.displayMedium
प्रॉपर्टी, डिसप्ले टेक्स्ट के लिए एक बड़ी स्टाइल है. यहां display शब्द का इस्तेमाल टाइपोग्राफ़िक सेंस में किया गया है. जैसे, display typeface.displayMedium
के दस्तावेज़ में बताया गया है कि "डिसप्ले स्टाइल, छोटे और ज़रूरी टेक्स्ट के लिए रिज़र्व होती हैं"—यह हमारे इस्तेमाल के उदाहरण से मेल खाता है.- थीम की
displayMedium
प्रॉपर्टी, सिद्धांत के तौर परnull
हो सकती है. Dart, प्रोग्रामिंग की एक ऐसी भाषा है जिसमें नल वैल्यू को सुरक्षित तरीके से हैंडल किया जाता है. इसलिए, यह आपको उन ऑब्जेक्ट के तरीकों को कॉल करने की अनुमति नहीं देगा जो संभावित रूप सेnull
हैं. हालाँकि, इस मामले में!
ऑपरेटर ("बैंग ऑपरेटर") का इस्तेमाल करके, Dart को यह बताया जा सकता है कि आपको पता है कि क्या करना है. (इस मामले में,displayMedium
की वैल्यू null नहीं है. इसकी वजह इस कोडलैब के दायरे से बाहर है.) displayMedium
परcopyWith()
को कॉल करने पर, टेक्स्ट स्टाइल की कॉपी मिलती है. इसमें आपके तय किए गए बदलाव शामिल होते हैं. इस मामले में, सिर्फ़ टेक्स्ट का रंग बदला जा रहा है.- नया रंग पाने के लिए, ऐप्लिकेशन की थीम को फिर से ऐक्सेस करें. कलर स्कीम की
onPrimary
प्रॉपर्टी, एक ऐसा रंग तय करती है जो ऐप्लिकेशन के मुख्य रंग के ऊपर इस्तेमाल करने के लिए सही हो.
अब ऐप्लिकेशन कुछ ऐसा दिखना चाहिए:
अगर आपको लगता है, तो कार्ड में और बदलाव करें. यहां कुछ आइडिया दिए गए हैं:
copyWith()
की मदद से, टेक्स्ट के स्टाइल में रंग के अलावा और भी कई बदलाव किए जा सकते हैं. बदली जा सकने वाली प्रॉपर्टी की पूरी सूची पाने के लिए, अपने कर्सर कोcopyWith()
के ब्रैकेट में कहीं भी रखें औरCtrl+Shift+Space
(Win/Linux) याCmd+Shift+Space
(Mac) दबाएं.- इसी तरह,
Card
विजेट के बारे में ज़्यादा जानकारी बदली जा सकती है. उदाहरण के लिए,elevation
पैरामीटर की वैल्यू बढ़ाकर, कार्ड की शैडो को बड़ा किया जा सकता है. - अलग-अलग रंगों का इस्तेमाल करके देखें.
theme.colorScheme.primary
के अलावा,.secondary
,.surface
, और कई अन्य ऐप्लिकेशन भी उपलब्ध हैं. इन सभी रंगों केonPrimary
वर्शन उपलब्ध हैं.
सुलभता को बेहतर बनाना
Flutter, ऐप्लिकेशन को डिफ़ॉल्ट रूप से ऐक्सेस करने की सुविधा देता है. उदाहरण के लिए, हर Flutter ऐप्लिकेशन, TalkBack और VoiceOver जैसे स्क्रीन रीडर को ऐप्लिकेशन में मौजूद सभी टेक्स्ट और इंटरैक्टिव एलिमेंट सही तरीके से दिखाता है.
हालांकि, कभी-कभी कुछ काम करना ज़रूरी होता है. इस ऐप्लिकेशन के मामले में, स्क्रीन रीडर को जनरेट किए गए कुछ शब्दों के जोड़े का उच्चारण करने में समस्याएं आ सकती हैं. इंसानों को cheaphead में मौजूद दो शब्दों की पहचान करने में कोई समस्या नहीं होती. हालांकि, स्क्रीन रीडर इस शब्द के बीच में मौजूद ph को f के तौर पर पढ़ सकता है.
इसका एक समाधान यह है कि pair.asLowerCase
को "${pair.first} ${pair.second}"
से बदल दिया जाए. बाद वाला फ़ंक्शन, स्ट्रिंग इंटरपोलेशन का इस्तेमाल करके pair
में मौजूद दो शब्दों से एक स्ट्रिंग (जैसे कि "cheap head"
) बनाता है. एक शब्द के बजाय दो अलग-अलग शब्दों का इस्तेमाल करने से, यह पक्का किया जा सकता है कि स्क्रीन रीडर उन्हें सही तरीके से पहचानें. साथ ही, इससे दृष्टिबाधित लोगों को बेहतर अनुभव मिलता है.
हालांकि, आपको pair.asLowerCase
का विज़ुअल डिज़ाइन पसंद आ सकता है. टेक्स्ट विजेट के विज़ुअल कॉन्टेंट को ऐसे सिमैंटिक कॉन्टेंट से बदलने के लिए, Text
की semanticsLabel
प्रॉपर्टी का इस्तेमाल करें जो स्क्रीन रीडर के लिए ज़्यादा सही हो:
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
अब स्क्रीन रीडर, जनरेट किए गए हर शब्द जोड़े का सही उच्चारण करते हैं. हालांकि, यूज़र इंटरफ़ेस में कोई बदलाव नहीं होता. अपने डिवाइस पर स्क्रीन रीडर का इस्तेमाल करके, इसे आज़माएं.
यूज़र इंटरफ़ेस (यूआई) को बीच में अलाइन करना
अब जब रैंडम शब्द के जोड़े को विज़ुअल फ़्लेयर के साथ दिखाया गया है, तो इसे ऐप्लिकेशन की विंडो/स्क्रीन के बीच में रखने का समय आ गया है.
सबसे पहले, यह याद रखें कि BigCard
, Column
का हिस्सा है. डिफ़ॉल्ट रूप से, कॉलम में बच्चों को सबसे ऊपर रखा जाता है. हालांकि, हम इस सेटिंग को बदल सकते हैं. MyHomePage
के build()
तरीके पर जाएं और यह बदलाव करें:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
इससे, Column
के अंदर मौजूद चाइल्ड विजेट, इसकी मुख्य (वर्टिकल) ऐक्सिस के बीच में आ जाते हैं.
बच्चों को कॉलम के क्रॉस ऐक्सिस के हिसाब से पहले से ही बीच में रखा गया है. दूसरे शब्दों में कहें, तो उन्हें पहले से ही हॉरिज़ॉन्टल तौर पर बीच में रखा गया है. हालांकि, Column
खुद Scaffold
के बीच में नहीं है. विजेट इंस्पेक्टर का इस्तेमाल करके, इसकी पुष्टि की जा सकती है.
विजेट इंस्पेक्टर, इस कोडलैब के दायरे से बाहर है. हालांकि, यहां देखा जा सकता है कि जब Column
को हाइलाइट किया जाता है, तो यह ऐप्लिकेशन की पूरी चौड़ाई नहीं लेता है. यह सिर्फ़ उतना हॉरिज़ॉन्टल स्पेस लेता है जितना इसके चाइल्ड को चाहिए.
सिर्फ़ कॉलम को बीच में अलाइन किया जा सकता है. अपने कर्सर को Column
पर रखें. इसके बाद, Ctrl+.
या Cmd+.
की मदद से Refactor मेन्यू को कॉल करें और Wrap with Center को चुनें.
अब ऐप्लिकेशन कुछ ऐसा दिखना चाहिए:
अगर आपको लगता है कि इसमें कुछ और बदलाव किए जा सकते हैं, तो ऐसा करें.
- ऊपर मौजूद
Text
विजेट कोBigCard
से हटाया जा सकता है. यह तर्क दिया जा सकता है कि जानकारी देने वाले टेक्स्ट ("एक शानदार आइडिया:") की अब ज़रूरत नहीं है, क्योंकि यूज़र इंटरफ़ेस (यूआई) इसके बिना भी समझ में आता है. इससे कोड ज़्यादा साफ़ दिखता है. BigCard
औरElevatedButton
के बीच,SizedBox(height: 10)
विजेट भी जोड़ा जा सकता है. इस तरह, दोनों विजेट के बीच थोड़ी ज़्यादा जगह होती है.SizedBox
विजेट सिर्फ़ जगह लेता है और खुद से कुछ भी रेंडर नहीं करता. आम तौर पर, इसका इस्तेमाल विज़ुअल "गैप" बनाने के लिए किया जाता है.
बदलाव करने के बाद, MyHomePage
में यह कोड शामिल होगा:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
ऐप्लिकेशन ऐसा दिखता है:
अगले सेक्शन में, जनरेट किए गए शब्दों को पसंदीदा बनाने (या ‘पसंद करें') की सुविधा जोड़ी जाएगी.
6. सुविधा जोड़ना
यह ऐप्लिकेशन काम करता है. साथ ही, कभी-कभी शब्दों के दिलचस्प जोड़े भी उपलब्ध कराता है. हालांकि, जब भी उपयोगकर्ता अगला पर क्लिक करता है, तो शब्दों का हर जोड़ा हमेशा के लिए गायब हो जाता है. बेहतरीन सुझावों को "याद रखने" का कोई तरीका होना चाहिए. जैसे, 'पसंद करें' बटन.
कारोबारी नियम जोड़ना
MyAppState
तक स्क्रोल करें और यह कोड जोड़ें:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
बदलावों की जांच करें:
- आपने
MyAppState
मेंfavorites
नाम की एक नई प्रॉपर्टी जोड़ी है. इस प्रॉपर्टी को खाली सूची के साथ शुरू किया गया है:[]
. - आपने यह भी बताया है कि सूची में सिर्फ़ शब्दों के जोड़े शामिल किए जा सकते हैं:
<WordPair>[]
. इसके लिए, जेनेरिक का इस्तेमाल किया गया है. इससे आपका ऐप्लिकेशन ज़्यादा मज़बूत बनता है. अगर आपने अपने ऐप्लिकेशन मेंWordPair
के अलावा कोई और वैल्यू जोड़ने की कोशिश की, तो Dart उसे चलाने से भी मना कर देगा. इसके बाद,favorites
सूची का इस्तेमाल किया जा सकता है. आपको पता होगा कि इसमें कभी भी कोई अनचाही चीज़ (जैसे किnull
) नहीं छिपी होगी.
- आपने
toggleFavorite()
नाम का एक नया तरीका भी जोड़ा है. इससे, शब्दों के मौजूदा जोड़े को पसंदीदा शब्दों की सूची से हटाया जा सकता है. ऐसा तब होता है, जब वह जोड़ा पहले से ही सूची में मौजूद हो. इसके अलावा, अगर वह जोड़ा सूची में मौजूद नहीं है, तो उसे जोड़ा जा सकता है. दोनों ही मामलों में, कोड इसके बादnotifyListeners();
को कॉल करता है.
बटन जोड़ना
"बिज़नेस लॉजिक" को अलग करने के बाद, अब यूज़र इंटरफ़ेस पर फिर से काम करने का समय है. ‘अगला' बटन के बाईं ओर ‘पसंद करें' बटन रखने के लिए, Row
की ज़रूरत होती है. Row
विजेट, Column
के हॉरिज़ॉन्टल वर्शन जैसा होता है. इसे आपने पहले देखा था.
सबसे पहले, मौजूदा बटन को Row
में रैप करें. MyHomePage
के build()
तरीके पर जाएं. इसके बाद, अपने कर्सर को ElevatedButton
पर रखें. Ctrl+.
या Cmd+.
की मदद से, रीफ़ैक्टर करें मेन्यू को कॉल करें. इसके बाद, Wrap with Row को चुनें.
सेव करने पर, आपको दिखेगा कि Row
, Column
की तरह काम करता है. डिफ़ॉल्ट रूप से, यह अपने बच्चों को बाईं ओर रखता है. (Column
ने अपने बच्चों को सबसे ऊपर रखा है.) इस समस्या को ठीक करने के लिए, पहले की तरह ही तरीका इस्तेमाल किया जा सकता है. हालांकि, इस बार mainAxisAlignment
का इस्तेमाल करना होगा. हालांकि, शिक्षा देने के मकसद से mainAxisSize
का इस्तेमाल करें. इससे Row
को यह निर्देश मिलता है कि वह उपलब्ध पूरे हॉरिज़ॉन्टल स्पेस का इस्तेमाल न करे.
यह बदलाव करें:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
यूज़र इंटरफ़ेस (यूआई) पहले जैसा हो जाएगा.
इसके बाद, पसंद करें बटन जोड़ें और इसे toggleFavorite()
से कनेक्ट करें. चुनौती के तौर पर, पहले इसे खुद से करने की कोशिश करें. इसके लिए, नीचे दिए गए कोड ब्लॉक को न देखें.
अगर आपने इसे नीचे दिए गए तरीके से नहीं किया है, तो भी कोई दिक्कत नहीं है. असल में, अगर आपको कोई बड़ी चुनौती नहीं चाहिए, तो दिल वाले आइकॉन के बारे में चिंता न करें.
अगर आप इस कोड को चलाने में सफल नहीं होते हैं, तो कोई बात नहीं. यह Flutter के साथ आपका पहला घंटा है.
MyHomePage
में दूसरा बटन जोड़ने का एक तरीका यहां बताया गया है. इस बार, आइकॉन वाला बटन बनाने के लिए ElevatedButton.icon()
कंस्ट्रक्टर का इस्तेमाल करें. इसके बाद, build
तरीके के सबसे ऊपर, सही आइकॉन चुनें. यह इस बात पर निर्भर करता है कि मौजूदा शब्द का जोड़ा पहले से ही पसंदीदा सूची में है या नहीं. साथ ही, ध्यान दें कि दो बटन के बीच थोड़ी जगह छोड़ने के लिए, SizedBox
का फिर से इस्तेमाल किया गया है.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
ऐप्लिकेशन ऐसा दिखना चाहिए:
माफ़ करें, उपयोगकर्ता को पसंदीदा आइटम नहीं दिखते. अब हमें अपने ऐप्लिकेशन में एक नई स्क्रीन जोड़नी है. अगले सेक्शन में मिलते हैं!
7. नेविगेशन रेल जोड़ना
ज़्यादातर ऐप्लिकेशन, एक ही स्क्रीन पर सारी जानकारी नहीं दिखा सकते. यह ऐप्लिकेशन शायद ऐसा कर सकता है. हालांकि, शिक्षा के मकसद से, आपको उपयोगकर्ता की पसंदीदा चीज़ों के लिए एक अलग स्क्रीन बनानी होगी. दोनों स्क्रीन के बीच स्विच करने के लिए, आपको पहला StatefulWidget
लागू करना होगा.
इस चरण को जल्द से जल्द पूरा करने के लिए, MyHomePage
को दो अलग-अलग विजेट में बांटें.
MyHomePage
में मौजूद सभी कोड को चुनें, उसे मिटाएं, और उसकी जगह यह कोड डालें:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
सेव करने के बाद, आपको दिखेगा कि यूज़र इंटरफ़ेस (यूआई) का विज़ुअल हिस्सा तैयार है. हालांकि, यह काम नहीं करता. नेविगेशन रेल में मौजूद ♥︎ (दिल) पर क्लिक करने से कुछ नहीं होता.
बदलावों की जांच करें.
- सबसे पहले, ध्यान दें कि
MyHomePage
का पूरा कॉन्टेंट,GeneratorPage
नाम के नए विजेट में एक्सट्रैक्ट किया गया है.MyHomePage
विजेट का सिर्फ़Scaffold
हिस्सा एक्सट्रैक्ट नहीं किया गया है. - नई
MyHomePage
में दो बच्चों के साथ एकRow
शामिल है. पहला विजेटSafeArea
है और दूसराExpanded
विजेट है. SafeArea
यह पक्का करता है कि उसके चाइल्ड विजेट को हार्डवेयर नॉच या स्टेटस बार से न ढका जाए. इस ऐप्लिकेशन में, विजेटNavigationRail
के चारों ओर रैप होता है, ताकि नेविगेशन बटन को मोबाइल के स्टेटस बार से छिपाने से रोका जा सके. उदाहरण के लिए.NavigationRail
में जाकर,extended: false
लाइन कोtrue
में बदला जा सकता है. इससे आइकॉन के बगल में मौजूद लेबल दिखते हैं. आने वाले समय में, आपको यह पता चलेगा कि जब ऐप्लिकेशन में काफ़ी हॉरिज़ॉन्टल स्पेस हो, तो इसे अपने-आप कैसे किया जा सकता है.- नेविगेशन रेल में दो डेस्टिनेशन (होम और पसंदीदा) हैं. इनके आइकॉन और लेबल भी दिए गए हैं. यह मौजूदा
selectedIndex
के बारे में भी बताता है. चुने गए इंडेक्स के तौर पर शून्य चुनने पर, पहला डेस्टिनेशन चुना जाता है. चुने गए इंडेक्स के तौर पर एक चुनने पर, दूसरा डेस्टिनेशन चुना जाता है. इसी तरह, आगे भी डेस्टिनेशन चुने जाते हैं. फ़िलहाल, इसे शून्य पर सेट किया गया है. - नेविगेशन रेल से यह भी तय होता है कि जब उपयोगकर्ता,
onDestinationSelected
की मदद से किसी डेस्टिनेशन को चुनता है, तो क्या होता है. फ़िलहाल, ऐप्लिकेशन सिर्फ़print()
के साथ अनुरोध की गई इंडेक्स वैल्यू को आउटपुट करता है. Row
का दूसरा चाइल्डExpanded
विजेट है. बढ़े हुए विजेट, लाइनों और कॉलम में बहुत काम आते हैं. इनकी मदद से, ऐसे लेआउट बनाए जा सकते हैं जहां कुछ चाइल्ड विजेट सिर्फ़ उतना स्पेस लेते हैं जितना उन्हें चाहिए (इस मामले मेंSafeArea
) और अन्य विजेट को बाकी स्पेस का ज़्यादा से ज़्यादा हिस्सा लेना चाहिए (इस मामले मेंExpanded
).Expanded
विजेट को इस तरह से भी समझा जा सकता है कि वे "लालची" होते हैं. अगर आपको इस विजेट की भूमिका के बारे में बेहतर तरीके से जानना है, तोSafeArea
विजेट को किसी दूसरेExpanded
के साथ रैप करके देखें. इससे मिलने वाला लेआउट कुछ ऐसा दिखता है:
- दो
Expanded
विजेट, उपलब्ध हॉरिज़ॉन्टल स्पेस को आपस में बांट लेते हैं. भले ही, नेविगेशन रेल को सिर्फ़ बाईं ओर थोड़ी जगह की ज़रूरत हो. Expanded
विजेट के अंदर, रंगीनContainer
है. साथ ही, कंटेनर के अंदरGeneratorPage
है.
स्टेटलेस बनाम स्टेटफ़ुल विजेट
अब तक, MyAppState
ने आपकी राज्य की सभी ज़रूरतों को पूरा किया है. इसलिए, अब तक लिखे गए सभी विजेट, स्टेटलेस होते हैं. इनमें खुद की कोई भी ऐसी स्थिति शामिल नहीं होती जिसे बदला जा सकता हो. कोई भी विजेट अपने-आप नहीं बदल सकता. इसके लिए, उन्हें MyAppState
से गुज़रना होगा.
यह अब बदलने वाला है.
आपको नेविगेशन रेल के selectedIndex
की वैल्यू को सेव करने के लिए किसी तरीके की ज़रूरत होगी. आपको इस वैल्यू को onDestinationSelected
कॉलबैक से भी बदलने की सुविधा चाहिए.
MyAppState
की एक और प्रॉपर्टी के तौर पर, selectedIndex
को जोड़ा जा सकता है. और यह काम करेगा. हालांकि, अगर हर विजेट अपनी वैल्यू को इसमें सेव करता है, तो ऐप्लिकेशन की स्थिति तेज़ी से बदलती है.
कुछ स्टेट सिर्फ़ एक विजेट से जुड़ी होती हैं, इसलिए उन्हें उसी विजेट के साथ रहना चाहिए.
StatefulWidget
डालें. यह एक तरह का विजेट है, जिसमें State
होता है. सबसे पहले, MyHomePage
को स्टेटफ़ुल विजेट में बदलें.
अपने कर्सर को MyHomePage
की पहली लाइन पर रखें. यह वह लाइन है जो class MyHomePage...
से शुरू होती है. इसके बाद, Ctrl+.
या Cmd+.
का इस्तेमाल करके, फिर से व्यवस्थित करें मेन्यू खोलें. इसके बाद, Convert to StatefulWidget को चुनें.
आईडीई आपके लिए एक नई क्लास, _MyHomePageState
बनाता है. यह क्लास State
से मिलती है. इसलिए, यह अपनी वैल्यू मैनेज कर सकती है. (यह अपने-आप बदल सकता है.) यह भी ध्यान दें कि पुराने, स्टेटलेस विजेट का build
तरीका, अब _MyHomePageState
में चला गया है. यह विजेट में नहीं है. इसे हूबहू ट्रांसफ़र किया गया है. build
तरीके में कोई बदलाव नहीं किया गया है. अब यह किसी दूसरी जगह पर मौजूद है.
setState
नए स्टेटफ़ुल विजेट को सिर्फ़ एक वैरिएबल को ट्रैक करने की ज़रूरत होती है: selectedIndex
. _MyHomePageState
में ये तीन बदलाव करें:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
बदलावों की जांच करें:
- आपने एक नया वैरिएबल,
selectedIndex
बनाया है और उसे0
पर सेट किया है. - अब तक इस्तेमाल किए जा रहे हार्ड-कोड किए गए
0
के बजाय,NavigationRail
की डेफ़िनिशन में इस नए वैरिएबल का इस्तेमाल करें. - जब
onDestinationSelected
कॉलबैक को कॉल किया जाता है, तो नई वैल्यू को सिर्फ़ कंसोल पर प्रिंट करने के बजाय, उसेsetState()
कॉल के अंदरselectedIndex
को असाइन किया जाता है. यह कॉल, पहले इस्तेमाल किए गएnotifyListeners()
तरीके जैसा ही है. इससे यह पक्का होता है कि यूज़र इंटरफ़ेस (यूआई) अपडेट हो गया है.
नेविगेशन रेल अब उपयोगकर्ता के इंटरैक्शन पर प्रतिक्रिया देती है. हालांकि, दाईं ओर बड़ा किया गया हिस्सा पहले जैसा ही रहता है. ऐसा इसलिए है, क्योंकि कोड में selectedIndex
का इस्तेमाल यह तय करने के लिए नहीं किया जा रहा है कि कौनसी स्क्रीन दिखेगी.
selectedIndex का इस्तेमाल करना
_MyHomePageState
के build
तरीके के सबसे ऊपर, return Scaffold
से ठीक पहले यह कोड डालें:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
इस कोड की जांच करें:
- इस कोड में,
page
टाइप का एक नया वैरिएबलpage
बनाया गया है.Widget
- इसके बाद, स्विच स्टेटमेंट
page
में मौजूद मौजूदा वैल्यू के हिसाब से,page
को स्क्रीन असाइन करता है.selectedIndex
- फ़िलहाल,
FavoritesPage
उपलब्ध नहीं है. इसलिए,Placeholder
का इस्तेमाल करें. यह एक काम का विजेट है. इसे जहां भी रखा जाता है वहां एक आयत बनाता है और उसे क्रॉस कर देता है. इससे यूज़र इंटरफ़ेस (यूआई) के उस हिस्से को अधूरा माना जाता है.
- fail-fast सिद्धांत लागू करने पर, स्विच स्टेटमेंट यह भी पक्का करता है कि अगर
selectedIndex
न तो 0 है और न ही 1, तो गड़बड़ी दिखे. इससे आने वाले समय में बग को रोकने में मदद मिलती है. अगर आपने कभी नेविगेशन रेल में कोई नया डेस्टिनेशन जोड़ा है और इस कोड को अपडेट करना भूल गए हैं, तो प्रोग्राम डेवलपमेंट के दौरान क्रैश हो जाता है. इससे आपको यह अनुमान लगाने की ज़रूरत नहीं पड़ती कि चीज़ें क्यों काम नहीं कर रही हैं. साथ ही, आपको प्रोडक्शन में गड़बड़ी वाला कोड पब्लिश करने की अनुमति भी नहीं मिलती.
अब page
में वह विजेट मौजूद है जिसे आपको दाईं ओर दिखाना है. इससे आपको शायद यह अंदाज़ा लग गया होगा कि और क्या बदलाव करना है.
यहां _MyHomePageState
में एक बदलाव करने के बाद का वर्शन दिया गया है:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
अब ऐप्लिकेशन, हमारे GeneratorPage
और प्लेसहोल्डर के बीच स्विच करता है. यह प्लेसहोल्डर जल्द ही पसंदीदा पेज बन जाएगा.
जवाबदेही
इसके बाद, नेविगेशन रेल को रिस्पॉन्सिव बनाएं. इसका मतलब है कि जब लेबल दिखाने के लिए काफ़ी जगह हो, तो उन्हें अपने-आप दिखने दें. इसके लिए, extended: true
का इस्तेमाल करें.
Flutter कई ऐसे विजेट उपलब्ध कराता है जिनकी मदद से, आपके ऐप्लिकेशन अपने-आप रिस्पॉन्सिव बन जाते हैं. उदाहरण के लिए, Wrap
, Row
या Column
जैसा ही एक विजेट है. इसमें वर्टिकल या हॉरिज़ॉन्टल स्पेस कम होने पर, चाइल्ड विजेट अपने-आप अगली "लाइन" (इसे "रन" कहा जाता है) में रैप हो जाते हैं. FittedBox
एक ऐसा विजेट है जो आपकी तय की गई सेटिंग के हिसाब से, उपलब्ध जगह में अपने-आप फ़िट हो जाता है.
हालांकि, NavigationRail
के पास ज़रूरत के मुताबिक जगह होने पर भी, लेबल अपने-आप नहीं दिखते. ऐसा इसलिए, क्योंकि यह नहीं जान सकता कि हर कॉन्टेक्स्ट में ज़रूरत के मुताबिक जगह कितनी है. यह कॉल आपको, यानी डेवलपर को करना होगा.
मान लें कि आपको लेबल सिर्फ़ तब दिखाने हैं, जब MyHomePage
की चौड़ाई कम से कम 600 पिक्सल हो.
इस मामले में, इस्तेमाल किया जाने वाला विजेट LayoutBuilder
है. इससे, उपलब्ध जगह के हिसाब से विजेट ट्री को बदला जा सकता है.
ज़रूरी बदलाव करने के लिए, VS Code में Flutter के Refactor मेन्यू का फिर से इस्तेमाल करें. हालांकि, इस बार यह थोड़ा ज़्यादा पेचीदा है:
_MyHomePageState
केbuild
तरीके में, अपने कर्सर कोScaffold
पर रखें.Ctrl+.
(Windows/Linux) याCmd+.
(Mac) दबाकर, Refactor मेन्यू खोलें.- Wrap with Builder को चुनें और Enter दबाएं.
- हाल ही में जोड़े गए
Builder
का नाम बदलकरLayoutBuilder
करें. - कॉल बैक पैरामीटर की सूची को
(context)
से बदलकर(context, constraints)
करें.
LayoutBuilder
का builder
कॉलबैक, हर बार कंस्ट्रेंट बदलने पर कॉल किया जाता है. ऐसा तब होता है, जब:
- जब उपयोगकर्ता ऐप्लिकेशन की विंडो का साइज़ बदलता है
- जब उपयोगकर्ता अपने फ़ोन को पोर्ट्रेट मोड से लैंडस्केप मोड में घुमाता है या वापस पोर्ट्रेट मोड में लाता है
MyHomePage
के बगल में मौजूद कुछ विजेट का साइज़ बढ़ जाता है. इससेMyHomePage
के लिए उपलब्ध जगह कम हो जाती है
अब आपका कोड यह तय कर सकता है कि मौजूदा constraints
से क्वेरी करके लेबल दिखाना है या नहीं. _MyHomePageState
के build
तरीके में, एक लाइन का यह बदलाव करें:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
अब आपका ऐप्लिकेशन, स्क्रीन साइज़, ओरिएंटेशन, और प्लैटफ़ॉर्म जैसे एनवायरमेंट के हिसाब से काम करता है! दूसरे शब्दों में कहें, तो यह रिस्पॉन्सिव है!.
अब सिर्फ़ इतना काम बाकी है कि उस Placeholder
को पसंदीदा स्क्रीन से बदल दिया जाए. इसके बारे में अगले सेक्शन में बताया गया है.
8. नया पेज जोड़ें
क्या आपको Placeholder
विजेट याद है? हमने इसका इस्तेमाल पसंदीदा पेज के बजाय किया था.
अब इसे ठीक करने का समय आ गया है.
अगर आपको लगता है कि आप यह काम खुद कर सकते हैं, तो इसे खुद करके देखें. आपका लक्ष्य, favorites
की सूची को एक नए स्टेटलेस विजेट, FavoritesPage
में दिखाना है. इसके बाद, Placeholder
के बजाय उस विजेट को दिखाना है.
यहां कुछ सुझाव दिए गए हैं:
- अगर आपको स्क्रोल करने वाला
Column
चाहिए, तोListView
विजेट का इस्तेमाल करें. - ध्यान रखें कि
context.watch<MyAppState>()
का इस्तेमाल करके, किसी भी विजेट सेMyAppState
इंस्टेंस को ऐक्सेस किया जा सकता है. - अगर आपको कोई नया विजेट आज़माना है, तो
ListTile
मेंtitle
(आम तौर पर टेक्स्ट के लिए),leading
(आइकॉन या अवतार के लिए), औरonTap
(इंटरैक्शन के लिए) जैसी प्रॉपर्टी होती हैं. हालांकि, आपको पहले से पता है कि किन विजेट से इस तरह के इफ़ेक्ट मिलते हैं. - Dart, कलेक्शन लिटरल के अंदर
for
लूप का इस्तेमाल करने की अनुमति देता है. उदाहरण के लिए, अगरmessages
में स्ट्रिंग की सूची शामिल है, तो आपके पास इस तरह का कोड हो सकता है:
दूसरी ओर, अगर आपको फ़ंक्शनल प्रोग्रामिंग के बारे में ज़्यादा जानकारी है, तो Dart में messages.map((m) => Text(m)).toList()
की तरह कोड भी लिखा जा सकता है. इसके अलावा, विजेट की सूची बनाकर, build
तरीके का इस्तेमाल करके उसमें विजेट जोड़े जा सकते हैं.
पसंदीदा पेज को खुद जोड़ने का फ़ायदा यह है कि अपने फ़ैसले लेने से आपको ज़्यादा जानकारी मिलती है. इसका नुकसान यह है कि आपको ऐसी समस्या का सामना करना पड़ सकता है जिसे फ़िलहाल, खुद से हल नहीं किया जा सकता. याद रखें: फ़ेल होना कोई बुरी बात नहीं है. यह सीखने की प्रोसेस का एक ज़रूरी हिस्सा है. किसी को भी यह उम्मीद नहीं होती कि आप पहले घंटे में ही Flutter डेवलपमेंट में महारत हासिल कर लेंगे. आपको भी ऐसा नहीं सोचना चाहिए.
यहां फ़ेवरेट पेज को लागू करने का सिर्फ़ एक तरीका बताया गया है. इसे लागू करने का तरीका देखकर, आपको कोड के साथ एक्सपेरिमेंट करने की प्रेरणा मिलेगी. इससे यूज़र इंटरफ़ेस (यूआई) को बेहतर बनाया जा सकता है और उसे अपनी पसंद के मुताबिक बनाया जा सकता है.
यहां नई FavoritesPage
क्लास दी गई है:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
यहां बताया गया है कि विजेट क्या करता है:
- यह ऐप्लिकेशन की मौजूदा स्थिति के बारे में जानकारी देता है.
- अगर पसंदीदा सूची खाली है, तो बीच में यह मैसेज दिखता है: आपने अभी तक कोई पसंदीदा गेम नहीं चुना है.
- अगर ऐसा नहीं है, तो यह स्क्रोल की जा सकने वाली सूची दिखाता है.
- सूची की शुरुआत में एक खास जानकारी दी जाती है.उदाहरण के लिए, आपके पास पांच पसंदीदा आइटम हैं.
- इसके बाद, कोड सभी पसंदीदा आइटम को एक-एक करके प्रोसेस करता है और हर आइटम के लिए
ListTile
विजेट बनाता है.
अब आपको सिर्फ़ Placeholder
विजेट को FavoritesPage
से बदलना है. और यह लीजिए!
आपको इस ऐप्लिकेशन का फ़ाइनल कोड, GitHub पर मौजूद कोडलैब रेपो में मिल जाएगा.
9. अगले चरण
बधाई हो!
ख़ुद को देखो! आपने एक ऐसे स्कैफ़ोल्ड का इस्तेमाल किया है जो काम नहीं करता. इसमें एक Column
और दो Text
विजेट हैं. आपने इसे रिस्पॉन्सिव और शानदार छोटे ऐप्लिकेशन में बदल दिया.
हमने क्या-क्या कवर किया है
- Flutter के काम करने के तरीके के बारे में बुनियादी जानकारी
- Flutter में लेआउट बनाना
- उपयोगकर्ता के इंटरैक्शन (जैसे, बटन दबाना) को ऐप्लिकेशन के व्यवहार से कनेक्ट करना
- अपने Flutter कोड को व्यवस्थित रखना
- अपने ऐप्लिकेशन को रिस्पॉन्सिव बनाना
- अपने ऐप्लिकेशन का रंग-रूप और स्टाइल एक जैसा बनाए रखना
आगे क्या करना है?
- इस लैब के दौरान लिखे गए ऐप्लिकेशन के साथ ज़्यादा एक्सपेरिमेंट करें.
- उसी ऐप्लिकेशन के इस ऐडवांस वर्शन का कोड देखें. इससे आपको यह पता चलेगा कि ऐनिमेटेड सूचियां, ग्रेडिएंट, क्रॉस-फ़ेड वगैरह कैसे जोड़े जा सकते हैं.
- flutter.dev/learn पर जाकर, अपनी लर्निंग जर्नी को फ़ॉलो करें.