1. ভূমিকা
Flutter হল Google এর UI টুলকিট যা একটি একক কোডবেস থেকে মোবাইল, ওয়েব এবং ডেস্কটপের জন্য অ্যাপ্লিকেশন তৈরি করে। এই কোডল্যাবে, আপনি নিম্নলিখিত Flutter অ্যাপ্লিকেশন তৈরি করবেন:
এই অ্যাপ্লিকেশনটি "newstay", "lightstream", "mainbrake", অথবা "graypine" এর মতো সুন্দর নাম তৈরি করে। ব্যবহারকারী পরবর্তী নাম জিজ্ঞাসা করতে পারেন, বর্তমান নামটি পছন্দ করতে পারেন এবং একটি পৃথক পৃষ্ঠায় পছন্দের নামের তালিকা পর্যালোচনা করতে পারেন। অ্যাপটি বিভিন্ন স্ক্রিন আকারের জন্য প্রতিক্রিয়াশীল।
তুমি কি শিখবে
- ফ্লাটার কীভাবে কাজ করে তার মূল বিষয়গুলি
- ফ্লটারে লেআউট তৈরি করা
- ব্যবহারকারীর মিথস্ক্রিয়া (যেমন বোতাম টিপে) অ্যাপ আচরণের সাথে সংযুক্ত করা
- আপনার ফ্লাটার কোডটি সংগঠিত রাখা
- আপনার অ্যাপটিকে (বিভিন্ন স্ক্রিনের জন্য) প্রতিক্রিয়াশীল করে তোলা
- আপনার অ্যাপের একটি সামঞ্জস্যপূর্ণ চেহারা এবং অনুভূতি অর্জন করা
তুমি একটি মৌলিক ভারা দিয়ে শুরু করবে যাতে তুমি সরাসরি আকর্ষণীয় অংশগুলিতে যেতে পারো।

আর এখানে ফিলিপ তোমাকে পুরো কোডল্যাবটা দেখাচ্ছে!
ল্যাব শুরু করতে পরবর্তী ক্লিক করুন।
2. আপনার ফ্লটার পরিবেশ সেট আপ করুন
সম্পাদক
এই কোডল্যাবটিকে যতটা সম্ভব সহজ করে তুলতে, আমরা ধরে নিচ্ছি যে আপনি আপনার ডেভেলপমেন্ট পরিবেশ হিসেবে ভিজ্যুয়াল স্টুডিও কোড (ভিএস কোড) ব্যবহার করবেন। এটি বিনামূল্যে এবং সমস্ত প্রধান প্ল্যাটফর্মে কাজ করে।
অবশ্যই আপনার পছন্দের যেকোনো এডিটর ব্যবহার করা ঠিক আছে: অ্যান্ড্রয়েড স্টুডিও, অন্যান্য ইন্টেলিজে আইডিই, ইম্যাকস, ভিম, অথবা নোটপ্যাড++। এগুলো সবই ফ্লটারের সাথে কাজ করে।
এই কোডল্যাবের জন্য আমরা VS কোড ব্যবহার করার পরামর্শ দিচ্ছি কারণ নির্দেশাবলী ডিফল্টভাবে VS কোড-নির্দিষ্ট শর্টকাটগুলিতে লেখা থাকে। "X করার জন্য আপনার সম্পাদকে যথাযথ পদক্ষেপ নিন" এর পরিবর্তে "এখানে ক্লিক করুন" বা "এই কী টিপুন" এর মতো জিনিসগুলি বলা সহজ।

একটি উন্নয়ন লক্ষ্য নির্বাচন করুন
ফ্লাটার একটি মাল্টি-প্ল্যাটফর্ম টুলকিট। আপনার অ্যাপটি নিম্নলিখিত যেকোনো অপারেটিং সিস্টেমে চলতে পারে:
- আইওএস
- অ্যান্ড্রয়েড
- জানালা
- ম্যাকওএস
- লিনাক্স
- ওয়েব
তবে, এমন একটি অপারেটিং সিস্টেম বেছে নেওয়া সাধারণ অভ্যাস যার জন্য আপনি প্রাথমিকভাবে ডেভেলপ করবেন। এটি আপনার "ডেভেলপমেন্ট টার্গেট" - যে অপারেটিং সিস্টেমের উপর আপনার অ্যাপ ডেভেলপমেন্টের সময় চলে।

উদাহরণস্বরূপ, ধরুন আপনি একটি উইন্ডোজ ল্যাপটপ ব্যবহার করে একটি ফ্লটার অ্যাপ তৈরি করছেন। যদি আপনি আপনার ডেভেলপমেন্ট টার্গেট হিসেবে অ্যান্ড্রয়েড বেছে নেন, তাহলে আপনি সাধারণত একটি USB কেবল ব্যবহার করে আপনার উইন্ডোজ ল্যাপটপের সাথে একটি অ্যান্ড্রয়েড ডিভাইস সংযুক্ত করেন এবং আপনার অ্যাপ-ইন-ডেভেলপমেন্ট সেই সংযুক্ত অ্যান্ড্রয়েড ডিভাইসে চলে। তবে আপনি উইন্ডোজকে ডেভেলপমেন্ট টার্গেট হিসেবেও বেছে নিতে পারেন, যার অর্থ আপনার অ্যাপ-ইন-ডেভেলপমেন্ট আপনার এডিটরের পাশাপাশি একটি উইন্ডোজ অ্যাপ হিসেবে চলে।
আপনার ডেভেলপমেন্ট টার্গেট হিসেবে ওয়েব নির্বাচন করা লোভনীয় হতে পারে। এই পছন্দের নেতিবাচক দিক হল আপনি Flutter-এর সবচেয়ে কার্যকর ডেভেলপমেন্ট বৈশিষ্ট্যগুলির মধ্যে একটি হারাবেন: Stateful Hot Reload। Flutter ওয়েব অ্যাপ্লিকেশনগুলিকে হট-রিলোড করতে পারে না।
এখনই আপনার পছন্দ করুন। মনে রাখবেন: আপনি পরে যেকোনো সময় অন্যান্য অপারেটিং সিস্টেমে আপনার অ্যাপ চালাতে পারবেন। শুধু একটি স্পষ্ট ডেভেলপমেন্ট লক্ষ্য মাথায় রাখলে পরবর্তী পদক্ষেপটি আরও সহজ হয়ে ওঠে।
ফ্লাটার ইনস্টল করুন
Flutter SDK কীভাবে ইনস্টল করবেন তার সবচেয়ে হালনাগাদ নির্দেশাবলী সর্বদা docs.flutter.dev এ থাকে।
Flutter ওয়েবসাইটের নির্দেশাবলী কেবল SDK ইনস্টলেশনের জন্যই নয়, বরং ডেভেলপমেন্ট টার্গেট-সম্পর্কিত সরঞ্জাম এবং সম্পাদক প্লাগইনগুলিও অন্তর্ভুক্ত করে। মনে রাখবেন, এই কোডল্যাবের জন্য, আপনাকে কেবল নিম্নলিখিতগুলি ইনস্টল করতে হবে:
- Flutter SDK সম্পর্কে
- ফ্লাটার প্লাগইন সহ ভিজ্যুয়াল স্টুডিও কোড
- আপনার নির্বাচিত ডেভেলপমেন্ট টার্গেটের জন্য প্রয়োজনীয় সফ্টওয়্যার (উদাহরণস্বরূপ: উইন্ডোজ টার্গেট করার জন্য ভিজ্যুয়াল স্টুডিও , অথবা ম্যাকওএস টার্গেট করার জন্য এক্সকোড )
পরবর্তী বিভাগে, আপনি আপনার প্রথম ফ্লটার প্রকল্প তৈরি করবেন।
যদি আপনার এখন পর্যন্ত সমস্যা হয়ে থাকে, তাহলে সমস্যা সমাধানের জন্য (স্ট্যাকওভারফ্লো থেকে) এই প্রশ্নোত্তরগুলির কিছু আপনার সহায়ক হতে পারে।
সচরাচর জিজ্ঞাস্য
- আমি কিভাবে Flutter SDK এর পথ খুঁজে পাব?
- Flutter কমান্ড না পেলে আমি কী করব?
- "স্টার্টআপ লকটি মুক্ত করার জন্য আরেকটি ফ্লাটার কমান্ডের জন্য অপেক্ষা করছি" সমস্যাটি কীভাবে সমাধান করব?
- আমার অ্যান্ড্রয়েড এসডিকে ইনস্টলেশনটি কোথায় আছে তা আমি ফ্লটারকে কীভাবে বলব?
-
flutter doctor --android-licensesচালানোর সময় জাভা ত্রুটির সাথে আমি কীভাবে মোকাবিলা করব? - অ্যান্ড্রয়েড
sdkmanagerটুল না পাওয়া গেলে আমি কীভাবে তা মোকাবেলা করব? - "
cmdline-toolsকম্পোনেন্ট অনুপস্থিত" ত্রুটিটি কীভাবে মোকাবেলা করব? - অ্যাপল সিলিকন (M1) তে কোকোপডস কীভাবে চালাবো?
- VS কোডে সেভ করার সময় আমি কীভাবে অটোফরম্যাটিং অক্ষম করব?
৩. একটি প্রকল্প তৈরি করুন
আপনার প্রথম ফ্লাটার প্রকল্প তৈরি করুন
ভিজ্যুয়াল স্টুডিও কোড চালু করুন এবং কমান্ড প্যালেটটি খুলুন ( F1 অথবা Ctrl+Shift+P অথবা Shift+Cmd+P দিয়ে)। "flutter new" টাইপ করা শুরু করুন। Flutter: New Project কমান্ডটি নির্বাচন করুন।
এরপর, Application নির্বাচন করুন এবং তারপর একটি ফোল্ডার নির্বাচন করুন যেখানে আপনার প্রকল্প তৈরি করবেন। এটি আপনার হোম ডিরেক্টরি হতে পারে, অথবা C:\src\ মতো কিছু হতে পারে।
অবশেষে, আপনার প্রকল্পের নাম দিন। namer_app অথবা my_awesome_namer মতো কিছু।

Flutter এখন আপনার প্রোজেক্ট ফোল্ডার তৈরি করে এবং VS কোড এটি খোলে।
এখন আপনি অ্যাপের একটি বেসিক স্ক্যাফোল্ড দিয়ে ৩টি ফাইলের বিষয়বস্তু ওভাররাইট করবেন।
প্রাথমিক অ্যাপটি কপি এবং পেস্ট করুন
VS কোডের বাম দিকের ফলকে, নিশ্চিত করুন যে Explorer নির্বাচন করা আছে, এবং 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 ।

এর বিষয়বস্তু নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন:
বিশ্লেষণ_অপশন.ইয়ামল
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)],
),
);
}
}
এই ৫০ লাইনের কোডই এখন পর্যন্ত অ্যাপটির সম্পূর্ণতা।
পরবর্তী বিভাগে, অ্যাপ্লিকেশনটি ডিবাগ মোডে চালান এবং ডেভেলপ করা শুরু করুন।
৪. একটি বোতাম যোগ করুন
এই ধাপে একটি নতুন শব্দ জোড়া তৈরি করতে একটি পরবর্তী বোতাম যোগ করা হয়েছে।
অ্যাপটি চালু করুন
প্রথমে, lib/main.dart খুলুন এবং নিশ্চিত করুন যে আপনার টার্গেট ডিভাইসটি নির্বাচন করা আছে। VS কোডের নীচে ডান কোণে, আপনি একটি বোতাম পাবেন যা বর্তমান টার্গেট ডিভাইসটি দেখায়। এটি পরিবর্তন করতে ক্লিক করুন।
lib/main.dart খোলা থাকাকালীন, "play" খুঁজুন
VS কোডের উইন্ডোর উপরের ডানদিকের কোণায় বোতামটি ক্লিক করুন এবং এটিতে ক্লিক করুন।
প্রায় এক মিনিট পর, আপনার অ্যাপটি ডিবাগ মোডে চালু হবে। এটি এখনও তেমন কিছু মনে হচ্ছে না:

প্রথম হট রিলোড
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-এর বিখ্যাত স্টেটফুল Hot Reload at work। যখন আপনি কোনও সোর্স ফাইলে পরিবর্তনগুলি সংরক্ষণ করেন তখন Hot reload ট্রিগার হয়।
সচরাচর জিজ্ঞাস্য
- যদি VSCode-এ Hot Reload কাজ না করে?
- VSCode-এ হট রিলোডের জন্য কি আমাকে 'r' চাপতে হবে?
- হট রিলোড কি ওয়েবে কাজ করে?
- আমি কিভাবে "ডিবাগ" ব্যানারটি সরাবো?
একটি বোতাম যোগ করা হচ্ছে
এরপর, দ্বিতীয় Text ইনস্ট্যান্সের ঠিক নীচে, Column এর নীচে একটি বোতাম যুক্ত করুন।
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 কোডের ডিবাগ কনসোল একটি বোতাম টিপে! বার্তা দেখায়।
৫ মিনিটের মধ্যে একটি ফ্লটার ক্র্যাশ কোর্স
ডিবাগ কনসোল দেখা যতটা মজার, আপনি চাইবেন বোতামটি আরও অর্থপূর্ণ কিছু করুক। তবে, সেই বিষয়ে পৌঁছানোর আগে, 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 প্রসারিত করে। Widgets হল সেই উপাদান যা থেকে আপনি প্রতিটি 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()পদ্ধতি সংজ্ঞায়িত করে যা উইজেটের পরিস্থিতি পরিবর্তনের সাথে সাথে স্বয়ংক্রিয়ভাবে কল করা হয় যাতে উইজেট সর্বদা আপ টু ডেট থাকে। -
MyHomePagewatchপদ্ধতি ব্যবহার করে অ্যাপের বর্তমান অবস্থার পরিবর্তনগুলি ট্র্যাক করে। - প্রতিটি
buildপদ্ধতিতে একটি উইজেট অথবা (আরও সাধারণভাবে) একটি নেস্টেড উইজেট ট্রি ফেরত দিতে হবে। এই ক্ষেত্রে, শীর্ষ-স্তরের উইজেট হলScaffold। আপনি এই কোডল্যাবেScaffoldসাথে কাজ করতে যাচ্ছেন না, তবে এটি একটি সহায়ক উইজেট এবং এটি বাস্তব-বিশ্বের Flutter অ্যাপের বিশাল অংশে পাওয়া যায়। -
Columnহল Flutter-এর সবচেয়ে মৌলিক লেআউট উইজেটগুলির মধ্যে একটি। এটি যেকোনো সংখ্যক শিশুকে নিয়ে একটি কলামে উপরে থেকে নীচে রাখে। ডিফল্টরূপে, কলামটি দৃশ্যত তার শিশুগুলিকে উপরে রাখে। আপনি শীঘ্রই এটি পরিবর্তন করবেন যাতে কলামটি কেন্দ্রীভূত হয়। - আপনি প্রথম ধাপে এই
Textউইজেটটি পরিবর্তন করেছেন। - এই দ্বিতীয়
TextউইজেটটিappStateনেয় এবং সেই ক্লাসের একমাত্র সদস্য,current(যা একটিWordPair) অ্যাক্সেস করে।WordPairবেশ কিছু সহায়ক গেটার প্রদান করে, যেমনasPascalCaseবাasSnakeCase। এখানে, আমরাasLowerCaseব্যবহার করি তবে আপনি যদি বিকল্পগুলির মধ্যে একটি পছন্দ করেন তবে আপনি এখন এটি পরিবর্তন করতে পারেন। - লক্ষ্য করুন কিভাবে Flutter কোড ট্রেইলিং কমা ব্যবহার করে। এই বিশেষ কমাটি এখানে থাকার প্রয়োজন নেই, কারণ
childrenহল এই নির্দিষ্টColumnপ্যারামিটার তালিকার শেষ (এবং একমাত্র ) সদস্য। তবুও সাধারণত ট্রেইলিং কমা ব্যবহার করা একটি ভালো ধারণা: তারা আরও সদস্য যোগ করাকে তুচ্ছ করে তোলে, এবং তারা ডার্টের অটো-ফরম্যাটারের জন্য সেখানে একটি নতুন লাইন স্থাপনের ইঙ্গিত হিসেবেও কাজ করে। আরও তথ্যের জন্য, কোড ফর্ম্যাটিং দেখুন।
এরপর, আপনি বোতামটি স্টেটের সাথে সংযুক্ত করবেন।
তোমার প্রথম আচরণ
MyAppState এ স্ক্রোল করুন এবং একটি getNext পদ্ধতি যোগ করুন।
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
নতুন getNext() পদ্ধতিটি একটি নতুন র্যান্ডম WordPair দিয়ে current পুনরায় বরাদ্দ করে। এটি notifyListeners() ( ChangeNotifier) নামেও পরিচিত যা নিশ্চিত করে যে MyAppState দেখছেন এমন যে কেউ বিজ্ঞপ্তি পেয়েছেন।
এখন শুধু বাটনের কলব্যাক থেকে getNext পদ্ধতিটি কল করা বাকি।
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
এখনই অ্যাপটি সংরক্ষণ করুন এবং চেষ্টা করে দেখুন। প্রতিবার আপনি পরবর্তী বোতাম টিপলে এটি একটি নতুন র্যান্ডম শব্দ জোড়া তৈরি করবে।
পরবর্তী বিভাগে, আপনি ইউজার ইন্টারফেসটিকে আরও সুন্দর করে তুলবেন।
৫. অ্যাপটিকে আরও সুন্দর করে তুলুন
এই মুহূর্তে অ্যাপটি এইরকম দেখাচ্ছে।

দারুন না। অ্যাপটির কেন্দ্রবিন্দু—এলোমেলোভাবে তৈরি হওয়া শব্দের জোড়া—আরও দৃশ্যমান হওয়া উচিত। সর্বোপরি, আমাদের ব্যবহারকারীরা এই অ্যাপটি ব্যবহার করার মূল কারণ এটি! এছাড়াও, অ্যাপের বিষয়বস্তু অদ্ভুতভাবে কেন্দ্রের বাইরে, এবং পুরো অ্যাপটি একঘেয়েভাবে কালো এবং সাদা।
এই বিভাগটি অ্যাপের নকশা নিয়ে কাজ করে এই সমস্যাগুলির সমাধান করে। এই বিভাগের চূড়ান্ত লক্ষ্য হল নিম্নরূপ:

একটি উইজেট বের করুন
বর্তমান শব্দ জোড়া দেখানোর জন্য দায়ী লাইনটি এখন এইরকম দেখাচ্ছে: Text(appState.current.asLowerCase) । এটিকে আরও জটিল কিছুতে পরিবর্তন করার জন্য, এই লাইনটি একটি পৃথক উইজেটে বের করা একটি ভাল ধারণা। আপনার UI এর পৃথক লজিক্যাল অংশগুলির জন্য পৃথক উইজেট থাকা 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 কে বোঝায় না।
এখন, রিফ্যাক্টর মেনুটি কল করুন। ভিএস কোডে, আপনি এটি দুটি উপায়ের একটিতে করতে পারেন:
- আপনি যে কোডটি রিফ্যাক্টর করতে চান তার উপর ডান-ক্লিক করুন (এই ক্ষেত্রে
Text) এবং ড্রপ-ডাউন মেনু থেকে রিফ্যাক্টর... নির্বাচন করুন,
অথবা
- আপনার কার্সারটি আপনি যে পিস কোডটি রিফ্যাক্টর করতে চান ( এই ক্ষেত্রে
Text) সেখানে নিয়ে যান এবংCtrl+.(Win/Linux ) অথবাCmd+.(Mac ) টিপুন।
রিফ্যাক্টর মেনুতে, এক্সট্র্যাক্ট উইজেট নির্বাচন করুন। একটি নাম দিন, যেমন 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);
}
}
// ...
এই রিফ্যাক্টরিংয়ের পরেও অ্যাপটি কীভাবে কাজ করে চলেছে তা লক্ষ্য করুন।
একটি কার্ড যোগ করুন
এখন সময় এসেছে এই নতুন উইজেটটিকে এই বিভাগের শুরুতে আমরা যে সাহসী UI কল্পনা করেছিলাম তাতে রূপান্তরিত করার।
BigCard ক্লাস এবং এর মধ্যে build() পদ্ধতিটি খুঁজুন। আগের মতো, Text উইজেটের রিফ্যাক্টর মেনুটি কল করুন। তবে, এবার আপনি উইজেটটি এক্সট্র্যাক্ট করতে যাচ্ছেন না।
পরিবর্তে, Wrap with Padding নির্বাচন করুন। এটি Text উইজেটের চারপাশে একটি নতুন প্যারেন্ট উইজেট তৈরি করে যার নাম Padding । সংরক্ষণ করার পরে, আপনি দেখতে পাবেন যে র্যান্ডম শব্দটি ইতিমধ্যেই আরও বেশি শ্বাস-প্রশ্বাসের জায়গা পেয়েছে।
প্যাডিং ডিফল্ট মান 8.0 থেকে বাড়ান। উদাহরণস্বরূপ, প্রশস্ত প্যাডিংয়ের জন্য 20 এর মতো কিছু ব্যবহার করুন।
এরপর, এক স্তর উপরে যান। Padding উইজেটে আপনার কার্সার রাখুন, রিফ্যাক্টর মেনুটি টেনে আনুন এবং Wrap with widget... নির্বাচন করুন।
এটি আপনাকে মূল উইজেটটি নির্দিষ্ট করতে দেয়। "কার্ড" টাইপ করুন এবং এন্টার টিপুন।
এটি 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 এর জন্য বীজ রঙ পরিবর্তন করে এই রঙ এবং পুরো অ্যাপের রঙের স্কিম পরিবর্তন করতে পারেন।
লক্ষ্য করুন কিভাবে রঙটি মসৃণভাবে অ্যানিমেট হয়। একে বলা হয় একটি অন্তর্নিহিত অ্যানিমেশন । অনেক ফ্লাটার উইজেট মানগুলির মধ্যে মসৃণভাবে ইন্টারপোলেট করবে যাতে UI কেবল অবস্থার মধ্যে "লাফ" না করে।
কার্ডের নীচের উঁচু বোতামটিও রঙ পরিবর্তন করে। হার্ড-কোডিং মানগুলির বিপরীতে অ্যাপ-ওয়াইড Theme ব্যবহারের এটাই শক্তি।
টেক্সট থিম
কার্ডটিতে এখনও একটি সমস্যা রয়ে গেছে: লেখাটি খুব ছোট এবং এর রঙ পড়া কঠিন। এটি ঠিক করতে, 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 টাইপফেসে ।displayMediumএর ডকুমেন্টেশনে বলা হয়েছে যে "ডিসপ্লে স্টাইলগুলি ছোট, গুরুত্বপূর্ণ টেক্সটের জন্য সংরক্ষিত" - ঠিক আমাদের ব্যবহারের ক্ষেত্রে। - থিমের
displayMediumপ্রপার্টি তাত্ত্বিকভাবেnullহতে পারে। Dart, যে প্রোগ্রামিং ল্যাঙ্গুয়েজে আপনি এই অ্যাপটি লিখছেন, সেটি null-নিরাপদ, তাই এটি আপনাকে সম্ভাব্যnullঅবজেক্টের মেথড কল করতে দেবে না। তবে, এই ক্ষেত্রে, আপনি!অপারেটর ("bang অপারেটর") ব্যবহার করে 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 অ্যাপ সঠিকভাবে অ্যাপের সমস্ত টেক্সট এবং ইন্টারেক্টিভ উপাদানগুলিকে টকব্যাক এবং ভয়েসওভারের মতো স্ক্রিন রিডারগুলিতে উপস্থাপন করে।

তবে মাঝে মাঝে কিছু কাজ করতে হয়। এই অ্যাপের ক্ষেত্রে, স্ক্রিন রিডারের কিছু জেনারেটেড শব্দ জোড়া উচ্চারণে সমস্যা হতে পারে। যদিও cheaphead তে দুটি শব্দ সনাক্ত করতে মানুষের সমস্যা হয় না, একজন স্ক্রিন রিডার শব্দের মাঝখানে ph কে f হিসাবে উচ্চারণ করতে পারে।
একটি সমাধান হল pair.asLowerCase "${pair.first} ${pair.second}" দিয়ে প্রতিস্থাপন করা। pair এ থাকা দুটি শব্দ থেকে একটি স্ট্রিং (যেমন "cheap head" ) তৈরি করতে string interpolation ব্যবহার করা হয়। একটি যৌগিক শব্দের পরিবর্তে দুটি পৃথক শব্দ ব্যবহার করলে স্ক্রিন রিডাররা তাদের যথাযথভাবে সনাক্ত করতে পারে এবং দৃষ্টি প্রতিবন্ধী ব্যবহারকারীদের জন্য আরও ভালো অভিজ্ঞতা প্রদান করে।
তবে, আপনি 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}",
),
),
);
}
// ...
এখন, স্ক্রিন রিডাররা প্রতিটি জেনারেটেড শব্দ জোড়া সঠিকভাবে উচ্চারণ করে, তবুও UI একই থাকে। আপনার ডিভাইসে একটি স্ক্রিন রিডার ব্যবহার করে এটি কার্যকরভাবে চেষ্টা করুন।
UI কে কেন্দ্রীভূত করুন
এখন যেহেতু এলোমেলো শব্দ জোড়াটি যথেষ্ট চাক্ষুষ প্রতিভার সাথে উপস্থাপিত হয়েছে, তাই এটিকে অ্যাপের উইন্ডো/স্ক্রিনের মাঝখানে রাখার সময় এসেছে।
প্রথমে মনে রাখবেন যে 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 ভিতরে কেন্দ্রীভূত নয়। আমরা Widget Inspector ব্যবহার করে এটি যাচাই করতে পারি।
উইজেট ইন্সপেক্টর নিজেই এই কোডল্যাবের আওতার বাইরে, কিন্তু আপনি দেখতে পাচ্ছেন যে যখন Column হাইলাইট করা হয়, তখন এটি অ্যাপের পুরো প্রস্থ দখল করে না। এটি কেবলমাত্র তার বাচ্চাদের প্রয়োজন অনুসারে অনুভূমিক স্থান দখল করে।
আপনি কেবল কলামটিকে কেন্দ্র করে রাখতে পারেন। আপনার কার্সারটি Column এ রাখুন, Refactor মেনুটি কল করুন ( Ctrl+. অথবা Cmd+. দিয়ে), এবং Wrap with Center নির্বাচন করুন।
অ্যাপটি এখন নিচের মতো দেখতে হবে:

তুমি চাইলে এটাকে আরও একটু পরিবর্তন করতে পারো।
- আপনি
BigCardউপরে থাকাTextউইজেটটি সরিয়ে ফেলতে পারেন। এটি যুক্তিযুক্ত হতে পারে যে বর্ণনামূলক টেক্সট ("একটি এলোমেলো AWESOME ধারণা:") আর প্রয়োজন নেই কারণ এটি ছাড়াই UI অর্থপূর্ণ। এবং এটি এইভাবে আরও পরিষ্কার। - আপনি
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'),
),
],
),
),
);
}
}
// ...
এবং অ্যাপটি দেখতে নিচের মতো:

পরবর্তী বিভাগে, আপনি তৈরি করা শব্দগুলিকে পছন্দসই (অথবা 'লাইক') করার ক্ষমতা যোগ করবেন।
৬. কার্যকারিতা যোগ করুন
অ্যাপটি কাজ করে, এবং মাঝে মাঝে আকর্ষণীয় শব্দ জোড়াও সরবরাহ করে। কিন্তু যখনই ব্যবহারকারী Next এ ক্লিক করেন, প্রতিটি শব্দ জোড়া চিরতরে অদৃশ্য হয়ে যায়। সেরা পরামর্শগুলি "মনে রাখার" একটি উপায় থাকা ভালো: যেমন 'লাইক' বোতাম।

ব্যবসায়িক যুক্তি যোগ করুন
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'),
),
],
),
],
),
),
);
}
}
// ...
UI আগের অবস্থানে ফিরে এসেছে।

এরপর, Like বোতামটি যোগ করুন এবং এটি toggleFavorite() এর সাথে সংযুক্ত করুন। একটি চ্যালেঞ্জের জন্য, প্রথমে নীচের কোড ব্লকটি না দেখে নিজেই এটি করার চেষ্টা করুন।

ঠিক আছে, যদি তুমি ঠিক একইভাবে না করো যেমনটা নিচে করা হয়েছে। আসলে, যদি না তুমি সত্যিই কোন বড় চ্যালেঞ্জ চাও, তাহলে হার্ট আইকন নিয়ে চিন্তা করো না।
ব্যর্থ হওয়াও সম্পূর্ণ ঠিক আছে—সবকিছুর পরেও, ফ্লটারের সাথে এটিই তোমার প্রথম ঘন্টা।

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'),
),
],
),
],
),
),
);
}
}
// ...
অ্যাপটি দেখতে এইরকম হওয়া উচিত:
দুর্ভাগ্যবশত, ব্যবহারকারীরা পছন্দের জিনিসগুলি দেখতে পাচ্ছেন না। আমাদের অ্যাপে একটি সম্পূর্ণ আলাদা স্ক্রিন যুক্ত করার সময় এসেছে। পরবর্তী বিভাগে দেখা হবে!
৭. নেভিগেশন রেল যোগ করুন
বেশিরভাগ অ্যাপই সবকিছু একটি স্ক্রিনে ফিট করতে পারে না। এই বিশেষ অ্যাপটি সম্ভবত এটি করতে পারে, তবে শিক্ষামূলক উদ্দেশ্যে, আপনি ব্যবহারকারীর পছন্দের জন্য একটি পৃথক স্ক্রিন তৈরি করতে যাচ্ছেন। দুটি স্ক্রিনের মধ্যে স্যুইচ করার জন্য, আপনি আপনার প্রথম 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'),
),
],
),
],
),
);
}
}
// ...
সংরক্ষণ করা হলে, আপনি দেখতে পাবেন যে UI এর ভিজ্যুয়াল দিকটি প্রস্তুত - কিন্তু এটি কাজ করছে না। নেভিগেশন রেলে ♥︎ (হার্ট) ক্লিক করলে কিছুই হয় না।

পরিবর্তনগুলি পরীক্ষা করুন।
- প্রথমে লক্ষ্য করুন যে
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 আপনার সমস্ত state চাহিদা পূরণ করেছে। এই কারণেই আপনি এখন পর্যন্ত যত উইজেট লিখেছেন তার সবগুলো stateless । এগুলিতে নিজস্ব কোনও পরিবর্তনযোগ্য অবস্থা নেই। কোনও উইজেটই নিজেকে পরিবর্তন করতে পারে না - তাদের অবশ্যই MyAppState এর মাধ্যমে যেতে হবে।
এটা পরিবর্তন হতে চলেছে।
নেভিগেশন রেলের selectedIndex এর মান ধরে রাখার জন্য আপনার কিছু উপায় প্রয়োজন। আপনি onDestinationSelected কলব্যাকের মধ্যে থেকেও এই মানটি পরিবর্তন করতে সক্ষম হতে চান।
আপনি MyAppState এর আরেকটি বৈশিষ্ট্য হিসেবে selectedIndex যোগ করতে পারেন । এবং এটি কাজ করবে। কিন্তু আপনি কল্পনা করতে পারেন যে প্রতিটি উইজেট যদি তার মান সংরক্ষণ করে তবে অ্যাপ স্টেটটি দ্রুত যুক্তিসঙ্গতভাবে বৃদ্ধি পাবে।

কিছু অবস্থা শুধুমাত্র একটি একক উইজেটের সাথে প্রাসঙ্গিক, তাই এটি সেই উইজেটের সাথেই থাকা উচিত।
StatefulWidget লিখুন, এটি এক ধরণের উইজেট যার State আছে। প্রথমে, MyHomePage কে একটি stateful উইজেটে রূপান্তর করুন।
MyHomePage এর প্রথম লাইনে (যেটি class MyHomePage... দিয়ে শুরু হয়) আপনার কার্সারটি রাখুন, এবং Ctrl+. অথবা Cmd+. ব্যবহার করে Refactor মেনুটি কল করুন। তারপর, Convert to StatefulWidget নির্বাচন করুন।
IDE আপনার জন্য একটি নতুন ক্লাস তৈরি করবে, _MyHomePageState । এই ক্লাসটি State প্রসারিত করে, এবং তাই এর নিজস্ব মান পরিচালনা করতে পারে। (এটি নিজেই পরিবর্তন করতে পারে।) এছাড়াও লক্ষ্য করুন যে পুরানো, স্টেটলেস উইজেট থেকে build পদ্ধতিটি _MyHomePageState এ স্থানান্তরিত হয়েছে (উইজেটে থাকার পরিবর্তে)। এটিকে আক্ষরিক অর্থে স্থানান্তরিত করা হয়েছিল — build পদ্ধতির ভিতরে কিছুই পরিবর্তন হয়নি। এটি এখন কেবল অন্য কোথাও থাকে।
সেটস্টেট
নতুন স্টেটফুল উইজেটটিতে শুধুমাত্র একটি ভেরিয়েবল ট্র্যাক করতে হবে: selectedIndex । _MyHomePageState এ নিম্নলিখিত 3টি পরিবর্তন করুন:
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এ ইনিশিয়ালাইজ করুন। - আপনি
NavigationRailসংজ্ঞায় এখন পর্যন্ত ব্যবহৃত হার্ড-কোডেড0এর পরিবর্তে এই নতুন ভেরিয়েবলটি ব্যবহার করবেন। - যখন
onDestinationSelectedকলব্যাক কল করা হয়, তখন কেবল কনসোলে নতুন মান প্রিন্ট করার পরিবর্তে, আপনি এটি একটিsetState()কলের ভিতরেselectedIndexএ অ্যাসাইন করেন। এই কলটি পূর্বে ব্যবহৃতnotifyListeners()পদ্ধতির অনুরূপ - এটি নিশ্চিত করে যে UI আপডেট হচ্ছে।
নেভিগেশন রেল এখন ব্যবহারকারীর মিথস্ক্রিয়ায় সাড়া দেয়। কিন্তু ডানদিকের প্রসারিত এলাকাটি একই থাকে। কারণ কোডটি কোন স্ক্রিনটি প্রদর্শন করবে তা নির্ধারণ করতে 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ব্যবহার করুন; একটি সহজ উইজেট যা আপনি যেখানেই রাখুন সেখানে একটি ক্রস করা আয়তক্ষেত্র আঁকে, UI এর সেই অংশটিকে অসম্পূর্ণ হিসাবে চিহ্নিত করে।

- Applying the fail-fast principle , the switch statement also makes sure to throw an error if
selectedIndexis neither 0 or 1. This helps prevent bugs down the line. If you ever add a new destination to the navigation rail and forget to update this code, the program crashes in development (as opposed to letting you guess why things don't work, or letting you publish a buggy code into production).
Now that page contains the widget you want to show on the right, you can probably guess what other change is needed.
Here's _MyHomePageState after that single remaining change:
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.
),
),
],
),
);
}
}
// ...
The app now switches between our GeneratorPage and the placeholder that will soon become the Favorites page.
প্রতিক্রিয়াশীলতা
Next, make the navigation rail responsive. That is to say, make it automatically show the labels (using extended: true ) when there's enough room for them.

Flutter provides several widgets that help you make your apps automatically responsive. For example, Wrap is a widget similar to Row or Column that automatically wraps children to the next "line" (called "run") when there isn't enough vertical or horizontal space. There's FittedBox , a widget that automatically fits its child into available space according to your specifications.
But NavigationRail doesn't automatically show labels when there's enough space because it can't know what is enough space in every context. It's up to you, the developer, to make that call.
Say you decide to show labels only if MyHomePage is at least 600 pixels wide.
The widget to use, in this case, is LayoutBuilder . It lets you change your widget tree depending on how much available space you have.
Once again, use Flutter's Refactor menu in VS Code to make the required changes. This time, though, it's a little more complicated:
- Inside
_MyHomePageState'sbuildmethod, put your cursor onScaffold. - Call up the Refactor menu with
Ctrl+.(Windows/Linux) orCmd+.(Mac). - Select Wrap with Builder and press Enter .
- Modify the name of the newly added
BuildertoLayoutBuilder. - Modify the callback parameter list from
(context)to(context, constraints).
LayoutBuilder 's builder callback is called every time the constraints change. This happens when, for example:
- The user resizes the app's window
- The user rotates their phone from portrait mode to landscape mode, or back
- Some widget next to
MyHomePagegrows in size, makingMyHomePage's constraints smaller
Now your code can decide whether to show the label by querying the current constraints . Make the following single-line change to _MyHomePageState 's build method:
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,
),
),
],
),
);
});
}
}
// ...
Now, your app responds to its environment, such as screen size, orientation, and platform! In other words, it's responsive!.
The only work that remains is to replace that Placeholder with an actual Favorites screen. That's covered in the next section.
8. Add a new page
Remember the Placeholder widget we used instead of the Favorites page?
It's time to fix this.
If you feel adventurous, try to do this step by yourself. Your goal is to show the list of favorites in a new stateless widget, FavoritesPage , and then show that widget instead of the Placeholder .
Here are a few pointers:
- When you want a
Columnthat scrolls, use theListViewwidget. - Remember, access the
MyAppStateinstance from any widget usingcontext.watch<MyAppState>(). - If you also want to try a new widget,
ListTilehas properties liketitle(generally for text),leading(for icons or avatars) andonTap(for interactions). However, you can achieve similar effects with the widgets you already know. - Dart allows using
forloops inside collection literals. For example, ifmessagescontains a list of strings, you can have code like the following:

On the other hand, if you're more familiar with functional programming, Dart also lets you write code like messages.map((m) => Text(m)).toList() . And, of course, you can always create a list of widgets and imperatively add to it inside the build method.
The advantage of adding the Favorites page yourself is that you learn more by making your own decisions. The disadvantage is that you might run into trouble that you aren't yet able to solve by yourself. Remember: failing is okay, and is one of the most important elements of learning. Nobody expects you to nail Flutter development in your first hour, and neither should you.

What follows is just one way to implement the favorites page. How it's implemented will (hopefully) inspire you to play with the code—improve the UI and make it your own.
Here's the new FavoritesPage class:
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),
),
],
);
}
}
Here's what the widget does:
- It gets the current state of the app.
- If the list of favorites is empty, it shows a centered message: No favorites yet.
- Otherwise, it shows a (scrollable) list.
- The list starts with a summary (for example, You have 5 favorites. ).
- The code then iterates through all the favorites, and constructs a
ListTilewidget for each one.
All that remains now is to replace the Placeholder widget with a FavoritesPage . And voilá!
You can get the final code of this app in the codelab repo on GitHub.
9. Next steps
অভিনন্দন!
Look at you! You took a non-functional scaffold with a Column and two Text widgets, and made it into a responsive, delightful little app.

আমরা যা কভার করেছি
- The basics of how Flutter works
- Creating layouts in Flutter
- Connecting user interactions (like button presses) to app behavior
- Keeping your Flutter code organized
- Making your app responsive
- Achieving a consistent look & feel of your app
এরপর কী?
- Experiment more with the app you wrote during this lab.
- Look at the code of this advanced version of the same app, to see how you can add animated lists, gradients, cross-fades, and more.
- Follow your learning journey by going to flutter.dev/learn .