آموزش پیاده سازی Bloc 1.0 در فلاتر
تقریبا بعد از گذشت یک سال از توسعه پکیج bloc بالاخره نسخه پایدار 1.0 این کتابخانه منتشر شد که شامل تغییرات زیادی نسبت به نسخه های قبل تر از آن می باشد.
در این مطلب قصد داریم با همدیگه نگاهی به تغییرات نسخه جدید بلاک و همچنین پیاده سازی یک مثل ساده فلاتر با ورژن جدید کتابخونه Bloc و تمرین کنیم.
تبدیل Bloc ها به Stream
در نسخه جدید کتابخونه بلاک تمام Bloc ها در واقع Stream هستند. به این معنی هست که دیگه نیازی به نوشتن Stream<State> get state نداریم.
از این به بعد به طور مستقیم میتونید به تغییرات state بلاک گوش دهید.
bloc.listen((state({
}):
Bloc به عنوان Sink
در ورژن 1.0 کتابخونه بلاک تمام Bloc ها به عنوان Sink نیز میتوانند عمل کنند.
این پیاده سازی یعنی از کلمه کلیدی Add که در دستورات Sink قرار داشت نیز میتوانید از این به بعد برای بلاک ها هم استفاده کنید.
در نسخه جدید Bloc کلمه dispatch حذف شده و برای اضافه کردن ایونت جدیدی باید از کلمه add استفاده کنید.
bloc.add(Event());
تغییر دیگر جایگزین شدن دستور dispose با دستور close می باشد.
برای درک بهتر و تمرین پروژه عملی کوچیکی و با همدیگه انجام میدیم.
بعد از اینکه پروژه جدیدی ایجاد کردید کتابخونه های زیر را به برنامه اضافه کنید.
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^1.0.0
equatable: ^0.6.1
از کتابخونه Equatable برای مقایسه آبجکت های مختلف استفاده می کنیم.
پروژه ای که میسازیم کار نمایش اطلاعات آب و هوایی و انجام میده اما بصورت آفلاین و با اطلاعات نمایشی.
یک کلاس به نام Weather ایجاد میکنیم که از کلاس Equatable ارث بری خواهد کرد.
class Weather extends Equatable {
final String cityName;
final double temperatureCelsius;
final double temperatureFarenheit;
Weather({
@required this.cityName,
@required this.temperatureCelsius,
this.temperatureFarenheit,
});
@override
List get props => [
cityName,
temperatureCelsius,
temperatureFarenheit,
];
}
بخش get props به دلیل استفاده از کتابخونه Equatable هست و ما مشخص میکنیم این متد چه فیلدهایی و قرار هست برگردونه.
برای اینکه کدهای تمیز تری داشته باشیم کلاس جدیدی به نام WeatherRepository ایجاد میکنیم که منبع دریافت اطلاعات را مشخص می کند.
بلاک با این کلاس سر و کار دارد و این کلاس تعیین میکند که اطلاعات از کجا دریافت شوند برای مثال از اینترنت یا دیتابیس داخلی گوشی.
abstract class WeatherRepository {
Future fetchWeather(String cityName);
Future fetchDetailedWeather(String cityName);
}
گفتیم که قرار هست اطلاعاتی که در این پروژه نمایش میدهیم واقعی نباشه. پس یک کلاس برای ایجاد دیتا های ساختگی میسازیم.
class FakeWeatherRepository implements WeatherRepository {
double cachedTempCelsius;
@override
Future fetchWeather(String cityName) {
// Simulate network delay
return Future.delayed(
Duration(seconds: 1),
() {
final random = Random();
// Simulate some network error
if (random.nextBool()) {
throw NetworkError();
}
// Since we're inside a fake repository, we need to cache the temperature
// in order to have the same one returned for the detailed weather
cachedTempCelsius = 20 + random.nextInt(15) + random.nextDouble();
// Return "fetched" weather
return Weather(
cityName: cityName,
// Temperature between 20 and 35.99
temperatureCelsius: cachedTempCelsius,
);
},
);
}
@override
Future fetchDetailedWeather(String cityName) {
return Future.delayed(
Duration(seconds: 1),
() {
return Weather(
cityName: cityName,
temperatureCelsius: cachedTempCelsius,
temperatureFarenheit: cachedTempCelsius * 1.8 + 32,
);
},
);
}
}
class NetworkError extends Error {}
در مورد الگوی Bloc بلاک در فلاتر قبلا صحبت کردیم و برای آشنا شدن با مفاهیم اولیه بهتر هست که به آن مراجعه کنید.
قبل از پیاده سازی بخش منطقی برنامه باید درباره نیازمندی ها و اکشن های برنامه فکر کنید تا بتونید بخش Event ها را تکمیل کنید.
برای مثال در این پروژه با رویدادهای مختلفی مثل دریافت وضعیت کلی هوا, دریافت جزییات وضعیت هوا و… سر و کار داریم.
import 'package:equatable/equatable.dart';
abstract class WeatherEvent extends Equatable {
const WeatherEvent();
}
class GetWeather extends WeatherEvent {
final String cityName;
const GetWeather(this.cityName);
@override
List get props => [
cityName
];
}
}
class GetDetailedWeather extends WeatherEvent {
final String cityName;
const GetDetailedWeather(this.cityName);
@override
List get props => [
cityName
];
}}
برای پیاده سازی بخش State اگر میخواید که زیاد دچار مشکل نشوید بهتره که از خودتون سوال کنید “در چند حالت مختلف State میتونه رابط کاربری و تغییر بده؟”
در این پروژه ما میخوایم کاربر با وارد کردن نام شهر بعد از نمایش یک لودینگ وضعیت هوا را در برنامه نمایش دهیم.
همچنین در هنگام نمایش اطلاعات ممکن هست که به خطا یا ارور هم برخورد کنیم.
- نام شهر
- لودینگ
- وضعیت هوا
- نمایش خطا
پس تقریبا 4 وضعیت مختلف State داریم.
abstract class WeatherState extends Equatable {
const WeatherState();
}
class WeatherInitial extends WeatherState {
const WeatherInitial();
@override
List get props => [];
}
class WeatherLoading extends WeatherState {
const WeatherLoading();
@override
List get props => [];
}
class WeatherLoaded extends WeatherState {
final Weather weather;
const WeatherLoaded(this.weather);
@override
List get props => [weather];
}
class WeatherError extends WeatherState {
final String message;
const WeatherError(this.message);
@override
List get props => [message];
}
هر کلاس Bloc باید دو بخش مهم و حتما پیاده سازی کند که شامل initialState و متد mapEventToState است.
متد mapEventToState قبلا شامل دو پارامتر بود که در بروزرسانی جدید پارامتر currentState حذف شده اما با دستور state خالی میتونید به آن دسترسی داشته باشید.
class WeatherBloc extends Bloc {
final WeatherRepository repository;
WeatherBloc(this.repository);
@override
WeatherState get initialState => WeatherInitial();
@override
Stream mapEventToState(
WeatherEvent event,
) async* {
// Emitting a state from the asynchronous generator
yield WeatherLoading();
// Branching the executed logic by checking the event type
if (event is GetWeather) {
// Emit either Loaded or Error
try {
final weather = await repository.fetchWeather(event.cityName);
yield WeatherLoaded(weather);
} on NetworkError {
yield WeatherError("Couldn't fetch weather. Is the device online?");
}
} else if (event is GetDetailedWeather) {
// Code duplication ? to keep the code simple for the tutorial...
try {
final weather = await repository.fetchDetailedWeather(event.cityName);
yield WeatherLoaded(weather);
} on NetworkError {
yield WeatherError("Couldn't fetch weather. Is the device online?");
}
}
}
}
برای طراحی رابط کاربری برنامه از ویجت BlocProvider استفاده میکنیم تا بر ساس State های مختلف ویجت های متفاوت و نمایش دهیم.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Weather App',
home: BlocProvider(
builder: (context) => WeatherBloc(FakeWeatherRepository()),
child: WeatherSearchPage(),
),
);
}
}
class WeatherSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Weather Search"),
),
body: Container(
padding: EdgeInsets.symmetric(vertical: 16),
alignment: Alignment.center,
child: BlocBuilder<weatherbloc, weatherstate="">(
builder: (context, state) {
if (state is WeatherInitial) {
return buildInitialInput();
} else if (state is WeatherLoading) {
return buildLoading();
} else if (state is WeatherLoaded) {
return buildColumnWithData(context, state.weather);
} else if (state is WeatherError) {
return buildInitialInput();
}
},
),
),
);
}
برای اینکه با هر بار تغییر اطلاعات بتونیم یک اسنکبار نمایش دهیم از BlocListener به شکل زیر استفاده میکنیم.
child: BlocListener<weatherbloc, weatherstate="">(
listener: (context, state) {
if (state is WeatherError) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
),
);
}
},
child: BlocBuilder<weatherbloc, weatherstate="">(
builder: (context, state) {
...
},
),
),
کلاس CityInputField یک ویجت اختصاصی هست که متدی به نام submitCityName دارد. این متد یک پارامتر به نام cityName دریافت و یک Event جدید ثبت میکند.
class CityInputField extends StatelessWidget {
...
void submitCityName(BuildContext context, String cityName) {
// Get the Bloc using the BlocProvider
// False positive lint warning, safe to ignore until it gets fixed...
final weatherBloc = BlocProvider.of<weatherbloc>(context);
// Initiate getting the weather
weatherBloc.add(GetWeather(cityName));
}
}
قصد داریم که با کلیک روی دکمه وارد صفحه جدیدی به نام WeatherDetailPage بشیم و اطلاعات آب و هوا را نمایش دهیم.
RaisedButton(
child: Text('See Details'),
color: Colors.lightBlue[100],
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<weatherbloc>(context),
child: WeatherDetailPage(
masterWeather: weather,
),
),
));
},
),
برای اینکار از BlocProvider.value استفاده میکنیم اما باید توجه داشته باشید حتما در صفحه قبلی باید builder پیاده سازی شده باشد.
مطالب زیر را حتما مطالعه کنید
آموزش پیاده سازی دیتابیس ObjectBox در فلاتر
آموزش اتصال اپلیکیشن فلاتر به پرینتر بلوتوثی
آموزش پیاده سازی معماری MVVM در فلاتر
روش های افزایش امنیت اپلیکیشن در فلاتر
آموزش کار با پکیج Freezed در فلاتر
آموزش ویجت SafeArea در فلاتر
6 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
سپاس از آموزش
عالی بود ممنون
یهو از صفر به 1000 رفتید توی این آموزش اگر همون increament رو آموزش میدادین بهتر نبود؟
سلام, توضیحات پایه ای در نسخه قبلی Bloc گفته شده میتونید از اون استفاده کنید اینجا فقط تغییرات کتابخانه گفته شده.
Cubit چی شد پس ؟
cubit تازه معرفی شده، این تاپیک قدیمی هستش، میتونی از سایت خود bloc یاد بگیری bloclibrary.dev