একটি Flutter প্রোটোটাইপ তৈরি করা সহজ৷ একটি উত্পাদন-প্রস্তুত Flutter অ্যাপ্লিকেশন তৈরি করতে যা স্কেল করে, লোডের অধীনে ভাল কার্য সম্পাদন করে এবং বছরের পর বছর ধরে রক্ষণাবেক্ষণযোগ্য হয় আর্কিটেকচার, রাষ্ট্র পরিচালনা, পরীক্ষা এবং স্থাপনার কর্মপ্রবাহ সম্পর্কে আরও গভীর বোঝার প্রয়োজন। এই নির্দেশিকা টিউটোরিয়াল প্রকল্প এবং বাস্তব-বিশ্বের অ্যাপ্লিকেশনগুলির মধ্যে ব্যবধান পূরণ করে, পেশাদার Flutter দলগুলি প্রতিদিন নির্ভর করে এমন নিদর্শন এবং অনুশীলনগুলিকে কভার করে৷ Flutter
এর জন্য
ক্লিন আর্কিটেকচার ক্লিন আর্কিটেকচার আপনার অ্যাপ্লিকেশানকে স্পষ্ট সীমানা এবং নির্ভরতা নিয়মের সাথে আলাদা স্তরে আলাদা করে। এই বিচ্ছেদ আপনার কোডকে পরীক্ষাযোগ্য, রক্ষণাবেক্ষণযোগ্য এবং বাহ্যিক কাঠামো এবং সরঞ্জামগুলির থেকে স্বাধীন করে তোলে।লেয়ার স্ট্রাকচার
একটি প্রোডাকশন Flutter অ্যাপ সাধারণত তিন-স্তর আর্কিটেকচার অনুসরণ করে:
- প্রেজেন্টেশন লেয়ার- উইজেট, পৃষ্ঠা, এবং রাজ্য পরিচালনা এই স্তরটি ডোমেন স্তরের উপর নির্ভর করে তবে সরাসরি ডেটা উত্সের উপর নির্ভর করে না।
- ডোমেন স্তর- ব্যবসায়িক যুক্তি, সত্তা এবং ব্যবহারের ক্ষেত্রে৷ এই স্তরটির Flutter বা যেকোনো বাহ্যিক প্যাকেজের উপর শূন্য নির্ভরতা রয়েছে। এটি তথ্য স্তর প্রয়োগ করে সংগ্রহস্থল ইন্টারফেস (বিমূর্ত ক্লাস) সংজ্ঞায়িত করে।
- ডেটা লেয়ার- সংগ্রহস্থল বাস্তবায়ন, API ক্লায়েন্ট, স্থানীয় ডাটাবেস অ্যাক্সেস, এবং ডেটা মডেল (DTOs)। এই স্তরটি ডোমেইন স্তরে সংজ্ঞায়িত ইন্টারফেস প্রয়োগ করে।
lib/
core/
error/
exceptions.dart
failures.dart
network/
network_info.dart
usecases/
usecase.dart
features/
authentication/
data/
datasources/
auth_remote_datasource.dart
auth_local_datasource.dart
models/
user_model.dart
repositories/
auth_repository_impl.dart
domain/
entities/
user.dart
repositories/
auth_repository.dart
usecases/
login.dart
register.dart
logout.dart
presentation/
bloc/
auth_bloc.dart
auth_event.dart
auth_state.dart
pages/
login_page.dart
register_page.dart
widgets/
login_form.dartনির্ভরতার নিয়ম কঠোর: ভিতরের স্তরগুলি কখনই বাইরের স্তরগুলি সম্পর্কে জানে না৷ ডোমেইন স্তর বিমূর্ত সংগ্রহস্থল ইন্টারফেস সংজ্ঞায়িত করে, এবং ডেটা স্তর কংক্রিট বাস্তবায়ন প্রদান করে। নিয়ন্ত্রণের এই উল্টোকরণ আপনাকে ব্যবসায়িক যুক্তিকে স্পর্শ না করেই ডেটা উত্সগুলি অদলবদল করতে দেয়৷
স্টেট ম্যানেজমেন্ট: BLoC এবং Riverpod
সঠিক স্টেট ম্যানেজমেন্ট সলিউশন বেছে নেওয়া হল একটি Flutter প্রোজেক্টের সবচেয়ে প্রভাবশালী আর্কিটেকচারাল সিদ্ধান্তগুলির মধ্যে একটি।
BLoC প্যাটার্ন
BLoC (বিজনেস লজিক কম্পোনেন্ট) রাজ্য পরিচালনা করতে স্ট্রিম ব্যবহার করে। ঘটনা প্রবাহিত হয়, রাজ্যগুলি প্রবাহিত হয়। এই একমুখী ডেটা প্রবাহ রাজ্যের পরিবর্তনগুলিকে অনুমানযোগ্য এবং ডিবাগ করা সহজ করে তোলে।
// Events
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
final String email;
final String password;
LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}
// States
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
}
// BLoC
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final LoginUseCase loginUseCase;
final LogoutUseCase logoutUseCase;
AuthBloc({
required this.loginUseCase,
required this.logoutUseCase,
}) : super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
on<LogoutRequested>(_onLogoutRequested);
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
final result = await loginUseCase(
LoginParams(email: event.email, password: event.password),
);
result.fold(
(failure) => emit(AuthError(failure.message)),
(user) => emit(AuthAuthenticated(user)),
);
}
Future<void> _onLogoutRequested(
LogoutRequested event,
Emitter<AuthState> emit,
) async {
await logoutUseCase();
emit(AuthInitial());
}
}Riverpod
Riverpod রাষ্ট্র পরিচালনার জন্য আরও নমনীয়, কম্পাইল-নিরাপদ পদ্ধতির প্রস্তাব করে। প্রদানকারীর বিপরীতে, Riverpod উইজেট গাছের উপর নির্ভর করে না, এটি পরীক্ষা এবং রচনা করা সহজ করে তোলে।
// Define providers
final authRepositoryProvider = Provider<AuthRepository>((ref) {
return AuthRepositoryImpl(
remoteDatasource: ref.read(authRemoteDatasourceProvider),
localDatasource: ref.read(authLocalDatasourceProvider),
);
});
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.read(authRepositoryProvider));
});
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository _repository;
AuthNotifier(this._repository) : super(const AuthState.initial());
Future<void> login(String email, String password) async {
state = const AuthState.loading();
final result = await _repository.login(email, password);
state = result.fold(
(failure) => AuthState.error(failure.message),
(user) => AuthState.authenticated(user),
);
}
}
// Use in widgets
class LoginPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authStateProvider);
return authState.when(
initial: () => LoginForm(),
loading: () => const CircularProgressIndicator(),
authenticated: (user) => HomePage(user: user),
error: (message) => ErrorDisplay(message: message),
);
}
}ডিপেনডেন্সি ইনজেকশন
পরীক্ষাযোগ্য কোডের জন্য যথাযথ নির্ভরতা ইনজেকশন অপরিহার্য।get_itপ্যাকেজ একটি সাধারণ পরিষেবা লোকেটার সরবরাহ করে যা পরিষ্কার আর্কিটেকচারের সাথে ভাল কাজ করে।
final sl = GetIt.instance;
void initDependencies() {
// External
sl.registerLazySingleton(() => Dio()..interceptors.add(AuthInterceptor()));
sl.registerLazySingleton(() => InternetConnectionChecker());
// Data sources
sl.registerLazySingleton<AuthRemoteDatasource>(
() => AuthRemoteDatasourceImpl(dio: sl()),
);
sl.registerLazySingleton<AuthLocalDatasource>(
() => AuthLocalDatasourceImpl(secureStorage: sl()),
);
// Repositories
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(
remoteDatasource: sl(),
localDatasource: sl(),
networkInfo: sl(),
),
);
// Use cases
sl.registerLazySingleton(() => LoginUseCase(sl()));
sl.registerLazySingleton(() => RegisterUseCase(sl()));
// BLoCs
sl.registerFactory(() => AuthBloc(
loginUseCase: sl(),
logoutUseCase: sl(),
));
}API ডিওর সাথে একীকরণ
Dio হল সবচেয়ে জনপ্রিয় HTTP ক্লায়েন্ট, যা ইন্টারসেপ্টর, গ্লোবাল কনফিগারেশন এবং গ্লোবাল কনফিগারেশনের জন্য অফার করে। টাইপ-সেফ রিকোয়েস্ট এবং রেসপন্স হ্যান্ডলিং সহ আপনার API লেয়ার গঠন করুন।
class ApiClient {
final Dio _dio;
ApiClient(this._dio) {
_dio.options = BaseOptions(
baseUrl: Environment.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
headers: {'Content-Type': 'application/json'},
);
_dio.interceptors.addAll([
AuthInterceptor(),
LogInterceptor(requestBody: true, responseBody: true),
RetryInterceptor(dio: _dio, retries: 3),
]);
}
Future<T> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
required T Function(dynamic data) parser,
}) async {
try {
final response = await _dio.get(path, queryParameters: queryParameters);
return parser(response.data);
} on DioException catch (e) {
throw _handleError(e);
}
}
AppException _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
return NetworkException('Connection timed out');
case DioExceptionType.badResponse:
return ServerException(
error.response?.statusCode ?? 500,
error.response?.data?['message'] ?? 'Unknown error',
);
default:
return NetworkException('Network error occurred');
}
}
}Hive এবং Sqflite সহ স্থানীয় স্টোরেজ
বেশিরভাগ প্রোডাকশন অ্যাপ্লিকেশানগুলির স্থানীয় ডেটা অধ্যবসায়ের প্রয়োজন৷ আপনার ডেটা জটিলতার উপর ভিত্তি করে সঠিক টুল নির্বাচন করুন। মূল-মান এবং অবজেক্ট স্টোরেজের জন্য
Hive
Hive হল একটি হালকা ওজনের, উজ্জ্বল-দ্রুত NoSQL ডাটাবেস যা বিশুদ্ধ ডার্টে লেখা। এটি ক্যাশিং, ব্যবহারকারীর পছন্দ এবং ছোট থেকে মাঝারি ডেটাসেট সংরক্ষণের জন্য আদর্শ। রিলেশনাল ডেটার জন্য
@HiveType(typeId: 0)
class CachedArticle extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String title;
@HiveField(2)
final String content;
@HiveField(3)
final DateTime cachedAt;
CachedArticle({
required this.id,
required this.title,
required this.content,
required this.cachedAt,
});
}
class ArticleCacheService {
static const _boxName = 'articles_cache';
Future<void> cacheArticles(List<Article> articles) async {
final box = await Hive.openBox<CachedArticle>(_boxName);
final cached = articles.map((a) => CachedArticle(
id: a.id,
title: a.title,
content: a.content,
cachedAt: DateTime.now(),
));
await box.clear();
await box.addAll(cached);
}
Future<List<CachedArticle>> getCachedArticles() async {
final box = await Hive.openBox<CachedArticle>(_boxName);
return box.values.toList();
}
}Sqflite
যখন আপনার ডেটাতে জটিল সম্পর্ক থাকে এবং আপনার SQL প্রশ্নগুলির প্রয়োজন হয়, তখন Sqflite Flutter এর জন্য একটি সম্পূর্ণ SQLite বাস্তবায়ন প্রদান করে৷ স্ট্রাকচার্ড ডেটার জন্য এটি ব্যবহার করুন যা যোগদান, সূচী এবং লেনদেন থেকে উপকৃত হয়।
পুশ বিজ্ঞপ্তিগুলি
সঠিক অনুমতি পরিচালনা এবং পটভূমি বার্তা প্রক্রিয়াকরণ সহ Firebase ক্লাউড মেসেজিং (FCM) ব্যবহার করে পুশ বিজ্ঞপ্তিগুলি প্রয়োগ করুন৷
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// Request permission
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// Get FCM token
final token = await _messaging.getToken();
await _sendTokenToServer(token);
// Listen for token refresh
_messaging.onTokenRefresh.listen(_sendTokenToServer);
// Handle foreground messages
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
// Handle background/terminated message taps
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageTap);
}
}
void _handleForegroundMessage(RemoteMessage message) {
// Show local notification using flutter_local_notifications
FlutterLocalNotificationsPlugin().show(
message.hashCode,
message.notification?.title,
message.notification?.body,
const NotificationDetails(
android: AndroidNotificationDetails(
'default_channel',
'Default',
importance: Importance.high,
),
),
);
}
}ডিপ লিঙ্কিং
ডিপ লিঙ্কিং ব্যবহারকারীদের বাহ্যিক URL থেকে সরাসরি আপনার অ্যাপের মধ্যে নির্দিষ্ট সামগ্রীতে নেভিগেট করতে দেয়৷ Flutter URI-ভিত্তিক গভীর লিঙ্ক এবং গতিশীল লিঙ্ক উভয়কেই সমর্থন করে।
// Configure in MaterialApp
MaterialApp(
onGenerateRoute: (settings) {
final uri = Uri.parse(settings.name ?? '');
if (uri.pathSegments.first == 'product') {
final productId = uri.pathSegments[1];
return MaterialPageRoute(
builder: (_) => ProductDetailPage(id: productId),
);
}
if (uri.pathSegments.first == 'order') {
final orderId = uri.pathSegments[1];
return MaterialPageRoute(
builder: (_) => OrderTrackingPage(id: orderId),
);
}
return MaterialPageRoute(builder: (_) => const HomePage());
},
)আরও শক্তিশালী গভীর লিঙ্কের জন্য,go_routerপ্যাকেজটি ব্যবহার করুন যা গভীর লিঙ্ক সমর্থন, পুনঃনির্দেশ এবং নেস্টেড নেভিগেশন সহ ঘোষণামূলক রাউটিং প্রদান করে। কোডম্যাজিক এবং ফাস্টলেনের সাথে
CI/CD
অটোমেটেড বিল্ড এবং ডিপ্লয়মেন্ট পাইপলাইন প্রোডাকশন অ্যাপের জন্য অপরিহার্য৷ Codemagic একটি Flutter-নেটিভ CI/CD পরিষেবা প্রদান করে, যখন Fastlane আরও কাস্টমাইজযোগ্য অটোমেশন অফার করে।
কোডম্যাজিক কনফিগারেশন
# codemagic.yaml
workflows:
production-release:
name: Production Release
max_build_duration: 60
environment:
flutter: stable
vars:
APP_STORE_CONNECT_KEY_ID: Encrypted(...)
GOOGLE_PLAY_SERVICE_ACCOUNT: Encrypted(...)
scripts:
- name: Install dependencies
script: flutter pub get
- name: Run tests
script: flutter test --coverage
- name: Build Android
script: flutter build appbundle --release
- name: Build iOS
script: |
flutter build ipa --release \
--export-options-plist=/path/to/ExportOptions.plist
artifacts:
- build/**/outputs/**/*.aab
- build/ios/ipa/*.ipa
publishing:
google_play:
credentials: $GOOGLE_PLAY_SERVICE_ACCOUNT
track: internal
app_store_connect:
api_key: $APP_STORE_CONNECT_KEY_IDফাস্টলেন ইন্টিগ্রেশন
ফাস্টলেন বিল্ড এবং জমা দেওয়ার প্রক্রিয়ার উপর দানাদার নিয়ন্ত্রণ প্রদান করে। বিভিন্ন প্রকাশের পর্যায়গুলির জন্য লেন সংজ্ঞায়িত করুন:
# fastlane/Fastfile
platform :ios do
desc "Deploy to TestFlight"
lane :beta do
build_flutter_app(target: "lib/main.dart")
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
desc "Deploy to App Store"
lane :release do
build_flutter_app(target: "lib/main.dart")
upload_to_app_store(
submit_for_review: true,
automatic_release: false
)
end
endপারফরম্যান্স প্রোফাইলিং
প্রোডাকশন অ্যাপ্লিকেশানগুলি সামঞ্জস্যপূর্ণ কর্মক্ষমতা দাবি করে৷ Flutter DevTools ব্যাপক প্রোফাইলিং ক্ষমতা প্রদান করে।
- উইজেট পুনর্নির্মাণ ট্র্যাকিং- অতিরিক্তভাবে পুনর্নির্মাণ করা উইজেটগুলি সনাক্ত করতে পারফরম্যান্স ওভারলে এবং DevTools ব্যবহার করুন৷ পুনর্নির্মাণগুলিকে ন্যূনতম করতে
constকনস্ট্রাক্টর এবং নির্বাচনী স্টেট ম্যানেজমেন্ট প্রয়োগ করুন৷ - ফ্রেম রেন্ডারিং- ফ্রেমগুলি 16ms (60fps) বা 8ms (120fps) এর মধ্যে রেন্ডার করা নিশ্চিত করতে টাইমলাইন ভিউ মনিটর করুন৷ ব্যয়বহুল বিল্ড, লেআউট এবং পেইন্ট পর্যায়গুলির জন্য দেখুন।
- মেমরি প্রোফাইলিং- লিক সনাক্ত করতে মেমরি বরাদ্দ ট্র্যাক করুন৷ সাধারণ অপরাধীদের মধ্যে রয়েছে বাতিলকৃত স্ট্রিম সাবস্ক্রিপশন, নিষ্পত্তি না করা কন্ট্রোলার এবং ক্লোজারে রাখা রেফারেন্স।
- স্টার্টআপ পারফরম্যান্স-
WidgetsBinding.instance.addPostFrameCallbackব্যবহার করে হেভি ইনিশিয়ালাইজেশন স্থগিত করুন। অবিলম্বে প্রয়োজন হয় না এমন বৈশিষ্ট্যগুলির জন্যdeferred asআমদানির সাথে বিলম্বিত লোডিং ব্যবহার করুন৷
// Profile-mode build for accurate performance measurement
// flutter run --profile
// Add performance overlay in debug builds
MaterialApp(
showPerformanceOverlay: true,
// ...
)উপসংহার
নির্মাণ-প্রস্তুত Flutter অ্যাপ্লিকেশনের জন্য উইজেট ক্যাটালগ জানার চেয়ে আরও বেশি কিছু প্রয়োজন৷ এটি চিন্তাশীল স্থাপত্য, শক্তিশালী রাষ্ট্র পরিচালনা, ব্যাপক পরীক্ষা এবং স্বয়ংক্রিয় স্থাপনার পাইপলাইন দাবি করে। পরিচ্ছন্ন আর্কিটেকচার গ্রহণ করে, সঠিক নির্ভরতা ইনজেকশনে বিনিয়োগ করে, সম্পূর্ণ ত্রুটি হ্যান্ডলিং বাস্তবায়ন করে এবং CI/CD ওয়ার্কফ্লো প্রতিষ্ঠা করে, আপনি এমন অ্যাপ্লিকেশন তৈরি করেন যা শুধুমাত্র কার্যকরী নয় কিন্তু দীর্ঘমেয়াদে রক্ষণাবেক্ষণযোগ্য এবং মাপযোগ্য।
আপনার আর্কিটেকচারকে প্রাথমিকভাবে স্থাপন করে শুরু করুন, প্রথম দিন থেকে পরীক্ষা লিখুন এবং আপনার প্রথম প্রকাশের আগে আপনার স্থাপনার পাইপলাইন স্বয়ংক্রিয় করুন। সময়ের সাথে সাথে এই অগ্রিম বিনিয়োগগুলি যৌগিক হয়ে যায়, যা আপনার দলকে কম রিগ্রেশন এবং প্রতিটি রিলিজে অধিক আত্মবিশ্বাসের সাথে দ্রুত বৈশিষ্ট্য পাঠাতে সক্ষম করে।