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 की विंडो में सबसे ऊपर दाएं कोने में मौजूद, "चलाएं"
बटन ढूंढें और उस पर क्लिक करें.
एक मिनट के बाद, आपका ऐप्लिकेशन डीबग मोड में लॉन्च हो जाएगा. फ़िलहाल, यह कुछ इस तरह दिखता है:

First Hot Reload
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को ऐक्सेस करता है.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 के लिए सीड कलर बदलें.
ध्यान दें कि रंग कितने स्मूद तरीके से ऐनिमेट हो रहा है. इसे इंप्लिसिट ऐनिमेशन कहा जाता है. कई Flutter विजेट, वैल्यू के बीच आसानी से इंटरपोलेट करते हैं, ताकि यूज़र इंटरफ़ेस (यूआई) सिर्फ़ स्थितियों के बीच "जंप" न करे.
कार्ड के नीचे मौजूद एलिवेटेड बटन का रंग भी बदल जाता है. यह, वैल्यू को हार्ड-कोड करने के बजाय, पूरे ऐप्लिकेशन के लिए 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प्रॉपर्टी, डिसप्ले टेक्स्ट के लिए बड़े स्टाइल वाली प्रॉपर्टी है. यहां डिसप्ले शब्द का इस्तेमाल टाइपोग्राफ़िक के तौर पर किया गया है. जैसे, डिसप्ले टाइपफ़ेस.displayMediumके दस्तावेज़ में बताया गया है कि "डिसप्ले स्टाइल, छोटे और ज़रूरी टेक्स्ट के लिए रिज़र्व होती हैं"—यह हमारे इस्तेमाल के उदाहरण से मेल खाता है.- सैद्धांतिक तौर पर, थीम की
displayMediumप्रॉपर्टीnullहो सकती है. डार्ट, एक प्रोग्रामिंग लैंग्वेज है जिसमें आप यह ऐप लिख रहे हैं, यह नल-सेफ़ है, इसलिए यह आपको उन ऑब्जेक्ट के तरीकों को कॉल करने की अनुमति नहीं देगा जो संभावित रूप से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 पर रखें. इसके बाद, Refactor मेन्यू (Ctrl+. या Cmd+. के साथ) को कॉल करें और Wrap with Center को चुनें.
अब ऐप्लिकेशन कुछ ऐसा दिखना चाहिए:

अगर आपको लगता है कि इसमें कुछ और बदलाव किए जा सकते हैं, तो ऐसा करें.
BigCardके ऊपर मौजूदTextविजेट को हटाया जा सकता है. यह तर्क दिया जा सकता है कि अब जानकारी देने वाले टेक्स्ट ("एक शानदार आइडिया:") की ज़रूरत नहीं है, क्योंकि यूज़र इंटरफ़ेस (यूआई) इसके बिना भी समझ में आता है. इससे कोड ज़्यादा साफ़ दिखता है.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+. की मदद से, Refactor मेन्यू को कॉल करें. इसके बाद, 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');
}
// ...
इस कोड की जांच करें:
- इस कोड में,
Widgetटाइप का एक नया वैरिएबलpageबनाया गया है. - इसके बाद, स्विच स्टेटमेंट,
selectedIndexमें मौजूद मौजूदा वैल्यू के हिसाब से,pageको स्क्रीन असाइन करता है. - फ़िलहाल,
FavoritesPageउपलब्ध नहीं है. इसलिए,Placeholderका इस्तेमाल करें. यह एक काम का विजेट है. इसे जहां भी रखा जाता है वहां एक क्रॉस किया गया आयत बनाता है. इससे यूज़र इंटरफ़ेस (यूआई) के उस हिस्से को अधूरा मार्क किया जाता है.

- fail-fast सिद्धांत लागू करने पर, switch स्टेटमेंट यह भी पक्का करता है कि अगर
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 पर मौजूद codelab repo में मिल सकता है.
9. अगले चरण
बधाई हो!
आपकी परफ़ॉर्मेंस लाजवाब है! आपने Column और दो Text विजेट के साथ एक नॉन-फ़ंक्शनल स्कैफ़ोल्ड लिया और उसे रिस्पॉन्सिव और शानदार छोटे ऐप्लिकेशन में बदल दिया.

हमने क्या-क्या कवर किया है
- Flutter के काम करने के तरीके के बारे में बुनियादी जानकारी
- Flutter में लेआउट बनाना
- उपयोगकर्ता के इंटरैक्शन (जैसे, बटन दबाना) को ऐप्लिकेशन के व्यवहार से कनेक्ट करना
- अपने Flutter कोड को व्यवस्थित रखना
- अपने ऐप्लिकेशन को रिस्पॉन्सिव बनाना
- अपने ऐप्लिकेशन का लुक और स्टाइल एक जैसा रखना
आगे क्या करना है?
- इस लैब के दौरान लिखे गए ऐप्लिकेशन के साथ ज़्यादा एक्सपेरिमेंट करें.
- उसी ऐप्लिकेशन के इस ऐडवांस वर्शन का कोड देखें. इससे आपको पता चलेगा कि ऐनिमेटेड सूचियां, ग्रेडिएंट, क्रॉस-फ़ेड वगैरह कैसे जोड़े जा सकते हैं.
- flutter.dev/learn पर जाकर, सीखने के अपने सफ़र को जारी रखें.