Reputation: 944
I have a screen where there are 2 dropdown widgets. When I select first dropdown and want to show a loading widget then I am getting error.
My problems are:
emit(currentState.copyWith(isDoctorLoading: true));
from _getCallCardDoctorListByTerritory method then doctor list is loaded but my selected territory reset automatically.There are too many codes, please pardon me. I thought all of it should be here for better understanding.
This is my Custom Tab Widget
class GlobalCustomTab extends StatelessWidget {
const GlobalCustomTab({
super.key,
required this.title,
required this.tabs,
required this.tabViews,
required this.onTabChangeActions,
});
final String title;
final List<Widget> tabs;
final List<Widget> tabViews;
final List<void Function(BuildContext)> onTabChangeActions;
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: tabs.length,
child: Builder(builder: (context) {
final TabController tabController = DefaultTabController.of(context);
tabController.addListener(() {
if (!tabController.indexIsChanging) {
_onTabChange(context, tabController.index, onTabChangeActions);
}
});
return Scaffold(
appBar: CustomAppbar(title: title),
body: Column(
children: [
TabBar(
unselectedLabelColor: AppColors.secondaryElementText,
labelStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
labelPadding: const EdgeInsets.symmetric(vertical: 16),
indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent,
onTap: (index) {
_onTabChange(context, index, onTabChangeActions);
},
tabs: tabs,
),
Expanded(
child: TabBarView(children: tabViews),
),
],
),
);
}),
);
}
void _onTabChange(BuildContext context, int index, List<void Function(BuildContext)> onTabChangeActions) {
if (index < onTabChangeActions.length) {
onTabChangeActions[index](context);
}
}
}
This is my main page where I am using that widget
class DailyCallCardPage extends StatelessWidget {
const DailyCallCardPage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => DoctorDailyCallCardBloc()..add(CallCardDoctorListEvent()),
),
BlocProvider(
create: (_) => ChemistDailyCallCardBloc()..add(CallCardChemistListEvent()),
),
],
child: GlobalCustomTab(
title: title,
tabs: const [
Text('DOCTOR'),
Text('CHEMIST'),
],
tabViews: [
const DoctorDailyCallCardPage(),
const ChemistDailyCallCardPage(),
],
onTabChangeActions: [
(context) => context
.read<DoctorDailyCallCardBloc>()
.add(CallCardDoctorListEvent()),
(context) => context
.read<ChemistDailyCallCardBloc>()
.add(CallCardChemistListEvent()),
],
),
);
}
}
This is Doctor Card widget which is the first tab view
class DoctorDailyCallCardPage extends StatelessWidget {
const DoctorDailyCallCardPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
builder: (context, state) {
if (state is DailyCallCardLoading) {
return const Center(child: LoadingWidget());
} else if (state is DailyCallCardError) {
return Center(child: CustomErrorWidget(message: state.message));
} else if (state is DailyCallCardLoaded) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: kHorizontal, vertical: elementPadding),
child: Column(
children: [
CreateCallCardWidget(),
_buildDivider(context),
if (state.isLoadingList!)
const Center(child: LoadingWidget())
else
CallCardListWidget(callCardList: state.doctorPlanList),
],
),
);
}
return const SizedBox.shrink();
},
);
}
Widget _buildDivider(BuildContext context) {
return CustomDivider(
title: 'Call Card',
widget: Flexible(
child: Row(
children: [
Image.asset(AppIcons.dateIcon),
Expanded(
child: CustomDatePicker(
onChanged: (value) {
context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorPlanListEvent(visitDate: value));
},
),
),
],
),
),
);
}
}
This is my create call card widget
class CreateCallCardWidget extends StatelessWidget {
CreateCallCardWidget({super.key});
final callCardKey = GlobalKey<FormState>();
final DailyCallCardReqParams params = DailyCallCardReqParams(
visitDate: DateFormat('yyyy-MM-dd', 'en').format(DateTime.now()),
visitTime: DateFormat('hh:mm a', 'en').format(DateTime.now()));
@override
Widget build(BuildContext context) {
return Container(
color: AppColors.secondaryElement,
padding: const EdgeInsets.symmetric(
horizontal: kHorizontal, vertical: elementPadding),
margin: const EdgeInsets.symmetric(vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
customTitle(title: 'Create Call Card', fontSize: 16.sp),
const Gap(18),
Form(
key: callCardKey,
child: Column(
children: [
CustomDateTimeRow(
onDateChanged: (value) {
params.visitDate = value;
},
onTimeChanged: (value) {
params.visitTime = value;
},
),
BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
builder: (context, state) {
if (state is DailyCallCardLoaded) {
return Column(
children: [
Visibility(
visible: state.showTerritory ?? false,
child: CustomDropdownSearch(
labelText: 'Select Territory',
items: (f, s) => state.territoryList,
itemAsString: (Territory data) => '${data.territoryName}\n${data.upperTerritoryCode}',
onChanged: (value) {
context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorListByTerritoryEvent(territoryCode: value!.upperTerritoryCode));
},
),
),
state.isDoctorLoading!
? const Center(child: LoadingWidget())
: CustomDropdownSearch(
labelText: 'Select Doctor',
items: (f, s) => state.doctorList,
itemAsString: (Doctor data) => data.name!,
onChanged: (value) {
params.doctorID = value!.doctorId;
},
),
CustomTextFormField(
hintText: 'Write a comment here....',
maxLines: 2,
onChanged: (value) {
params.opinion = value;
},
),
state.isSettingPlan!
? const ThreeDotLoading()
: CustomButton(
title: 'Set Plan',
onPressed: () {
if (callCardKey.currentState!.validate()) {
context
.read<DoctorDailyCallCardBloc>()
.add(SetCallCard(params: params));
}
},),
],
);
}
return const SizedBox.shrink();
},
),
],
),
),
],
),
);
}
}
class CustomDateTimeRow extends StatelessWidget {
const CustomDateTimeRow({
super.key,
this.onDateChanged,
this.onTimeChanged,
});
final Function(String?)? onDateChanged;
final Function(String?)? onTimeChanged;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 1,
child: CustomContainer(
height: 45.r,
child: Row(
children: [
Image.asset(AppIcons.dateIcon),
Expanded(
child: CustomDatePicker(
onChanged: onDateChanged,
),
),
],
),
),
),
const Gap(12),
Expanded(
flex: 1,
child: CustomContainer(
height: 45.r,
child: Row(
children: [
Image.asset(AppIcons.timeIcon),
Expanded(
child: CustomTimePicker(
onChanged: onTimeChanged,
),
),
],
),
),
)
],
);
}
}
This is my bloc class
class DoctorDailyCallCardBloc extends Bloc<DoctorDailyCallCardEvent, DoctorDailyCallCardState> {
String visitDate = '';
final String currentDate = DateTime.now().toString().split(' ')[0];
DoctorDailyCallCardBloc() : super(DailyCallCardInitial()) {
on<CallCardDoctorListEvent>(_getCallCardDoctorList);
on<CallCardDoctorListByTerritoryEvent>(_getCallCardDoctorListByTerritory);
on<SetCallCard>(_setDailyCallCard);
on<CallCardDoctorPlanListEvent>(_setDailyCallCardLoaded);
}
Future<void> _getCallCardDoctorList(CallCardDoctorListEvent event, Emitter<DoctorDailyCallCardState> emit) async {
emit(DailyCallCardLoading());
visitDate = event.visitDate ?? currentDate;
final String role = await GlobalStorage.getRole() ?? '';
List<Territory> territoryList = [];
List<Doctor> doctorList = [];
List<Doctor> doctorPlanList = [];
try {
// Initialize futures based on role
List<Future<Either>> futures = [
sl<GetDoctorPlanUseCase>().call(param: {'visitDate': visitDate}),
];
if (role.contains('T')) {
futures.add(sl<GetDoctorUseCase>().call());
} else {
futures.add(sl<AllTerritoryUseCase>().call());
}
// Execute all required use cases concurrently
final results = await Future.wait(futures);
// Extract results
final Either doctorPlanResult = results[0];
final Either doctorResult = results[1];
final Either? territoryResult = !role.contains('T') ? results[1] : null;
// Handle doctorResult
await doctorResult.fold((error) async {
emit(DailyCallCardError(message: error));
return;
}, (doctorResponse) async {
final DoctorEntity doctorData = DoctorEntity.fromJson(doctorResponse);
if (doctorData.status!) {
doctorList = doctorData.dataList ?? [];
}
});
// Handle doctorPlanResult
await doctorPlanResult.fold((error) async {
emit(DailyCallCardError(message: error));
return;
}, (doctorPlanResponse) async {
final DoctorEntity doctorPlanData = DoctorEntity.fromJson(doctorPlanResponse);
if (doctorPlanData.status!) {
doctorPlanList = doctorPlanData.dataList!;
}
});
// Handle territoryResult if applicable
if (territoryResult != null) {
await territoryResult.fold((error) async {
emit(DailyCallCardError(message: error));
}, (territoryResponse) async {
final AllTerritoryEntity territoryData = AllTerritoryEntity.fromJson(territoryResponse);
if (territoryData.status!) {
territoryList = territoryData.territoryList ?? [];
}
});
}
// Emit loaded state
emit(DailyCallCardLoaded(
doctorList: doctorList,
doctorPlanList: doctorPlanList,
territoryList: territoryList,
showTerritory: role.contains('T') ? false : true,
isDoctorLoading: false,
isSettingPlan: false,
isLoadingList: false,
));
} catch (e, stacktrace) {
emit(DailyCallCardError(message: e.toString()));
debugPrint('Error from $runtimeType: ${e.toString()}');
debugPrint('Stacktrace from $runtimeType: $stacktrace');
}
}
Future<void> _getCallCardDoctorListByTerritory(CallCardDoctorListByTerritoryEvent event, Emitter<DoctorDailyCallCardState> emit) async {
final currentState = state;
if (currentState is DailyCallCardLoaded) {
emit(currentState.copyWith(isDoctorLoading: true));
try {
final Either result = await sl<GetDoctorByTerritoryUseCase>().call(param: {'code': event.territoryCode});
// Initialize state data
List<Doctor> doctorList = [];
await result.fold((error) async {
emit(DailyCallCardError(message: error));
return;
}, (doctorByTerritoryResponse) async {
final DoctorEntity doctorData = DoctorEntity.fromJson(doctorByTerritoryResponse);
if (doctorData.status!) {
doctorList = doctorData.dataList!;
}
});
emit(currentState.copyWith(showTerritory: true, isDoctorLoading: false, doctorList: doctorList));
} catch (e, stacktrace) {
emit(DailyCallCardError(message: e.toString()));
debugPrint('Error from $runtimeType: ${e.toString()}');
debugPrint('Stacktrace from $runtimeType: $stacktrace');
}
}
}
Future<void> _setDailyCallCard(SetCallCard event, Emitter<DoctorDailyCallCardState> emit) async {
final currentState = state;
if (currentState is DailyCallCardLoaded) {
emit(currentState.copyWith(isSettingPlan: true));
Either result = await sl<SetDoctorPlanUseCase>().call(param: event.params);
await result.fold((error) async {
emit(DailyCallCardError(message: error));
}, (response) async {
final bool status = response['status'];
final String message = response['message'];
if (status) {
Utils.showToast(message);
emit(currentState.copyWith(isSettingPlan: false));
add(CallCardDoctorPlanListEvent(visitDate: visitDate));
}
});
}
}
Future<void> _setDailyCallCardLoaded(CallCardDoctorPlanListEvent event, Emitter<DoctorDailyCallCardState> emit) async {
final currentState = state;
if (currentState is DailyCallCardLoaded) {
emit(currentState.copyWith(isLoadingList: true));
visitDate = event.visitDate ?? currentDate;
try {
final Either result = await sl<GetDoctorPlanUseCase>().call(param: {'visitDate': visitDate});
// Initialize state data
List<Doctor> doctorPlanList = [];
await result.fold((error) async {
emit(DailyCallCardError(message: error));
return;
}, (doctorPlanResponse) async {
final DoctorEntity doctorData = DoctorEntity.fromJson(doctorPlanResponse);
if (doctorData.status!) {
doctorPlanList = doctorData.dataList!;
}
});
emit(currentState.copyWith(doctorPlanList: doctorPlanList, isLoadingList: false));
} catch (e, stacktrace) {
emit(DailyCallCardError(message: e.toString()));
debugPrint('Error from $runtimeType: ${e.toString()}');
debugPrint('Stacktrace from $runtimeType: $stacktrace');
}
}
}
}
This is state class
@immutable
sealed class DoctorDailyCallCardState extends Equatable {
@override
List<Object?> get props => [];
}
final class DailyCallCardInitial extends DoctorDailyCallCardState {}
final class DailyCallCardLoading extends DoctorDailyCallCardState {}
final class DailyCallCardByDoctorLoading extends DoctorDailyCallCardState {}
final class DailyCallCardLoaded extends DoctorDailyCallCardState {
final List<Doctor> doctorList;
final List<Doctor> doctorPlanList;
final List<Territory> territoryList;
final bool? isDoctorLoading;
final bool? isSettingPlan;
final bool? isLoadingList;
final bool? showTerritory;
DailyCallCardLoaded({
required this.doctorList,
required this.doctorPlanList,
required this.territoryList,
this.isDoctorLoading,
this.isSettingPlan = false,
this.isLoadingList = false,
this.showTerritory,
});
DailyCallCardLoaded copyWith({
List<Doctor>? doctorList,
List<Doctor>? doctorPlanList,
List<Territory>? territoryList,
bool? isDoctorLoading,
bool? isSettingPlan,
bool? isLoadingList,
bool? showTerritory,
}) {
return DailyCallCardLoaded(
doctorList: doctorList ?? this.doctorList,
doctorPlanList: doctorPlanList ?? this.doctorPlanList,
territoryList: territoryList ?? this.territoryList,
isDoctorLoading: isDoctorLoading ?? this.isDoctorLoading,
isSettingPlan: isSettingPlan ?? this.isSettingPlan,
isLoadingList: isLoadingList ?? this.isLoadingList,
showTerritory: showTerritory ?? this.showTerritory,
);
}
@override
List<Object?> get props => [
doctorList,
doctorPlanList,
territoryList,
isDoctorLoading,
isSettingPlan,
isLoadingList,
showTerritory,
];
}
final class DailyCallCardError extends DoctorDailyCallCardState {
final String message;
DailyCallCardError({
required this.message,
});
@override
List<Object> get props => [message];
}
This is the event
@immutable
sealed class DoctorDailyCallCardEvent extends Equatable {
@override
List<Object?> get props => [];
}
class CallCardDoctorListEvent extends DoctorDailyCallCardEvent {
final String? visitDate;
CallCardDoctorListEvent({
this.visitDate,
});
@override
List<Object?> get props => [visitDate];
}
class CallCardDoctorListByTerritoryEvent extends DoctorDailyCallCardEvent {
final String? territoryCode;
CallCardDoctorListByTerritoryEvent({
this.territoryCode,
});
@override
List<Object?> get props => [territoryCode];
}
class SetCallCard extends DoctorDailyCallCardEvent {
final DailyCallCardReqParams params;
SetCallCard({
required this.params,
});
@override
List<Object> get props => [params];
}
class CallCardDoctorPlanListEvent extends DoctorDailyCallCardEvent {
final String? visitDate;
CallCardDoctorPlanListEvent({
this.visitDate,
});
@override
List<Object?> get props => [visitDate];
}
Upvotes: 0
Views: 28
Reputation: 944
My problem is solved after changing my CreateCallCardWidget
from StatelessWidget
to StatefulWidget
and updating my onChanged
function of CustomDropDownWidget
.
class CreateCallCardWidget extends StatefulWidget {
const CreateCallCardWidget({super.key});
@override
State<CreateCallCardWidget> createState() => _CreateCallCardWidgetState();
}
class _CreateCallCardWidgetState extends State<CreateCallCardWidget> {
final callCardKey = GlobalKey<FormState>();
late DailyCallCardReqParams params;
Territory? selectedTerritory;
@override
void initState() {
super.initState();
params = DailyCallCardReqParams(
visitDate: DateFormat('yyyy-MM-dd', 'en').format(DateTime.now()),
visitTime: DateFormat('hh:mm a', 'en').format(DateTime.now()),
);
}
@override
Widget build(BuildContext context) {
return Container(
color: AppColors.secondaryElement,
padding: const EdgeInsets.symmetric(
horizontal: kHorizontal, vertical: elementPadding),
margin: const EdgeInsets.symmetric(vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
customTitle(title: 'Create Call Card', fontSize: 16.sp),
const Gap(18),
Form(
key: callCardKey,
child: Column(
children: [
CustomDateTimeRow(
onDateChanged: (value) {
params.visitDate = value;
},
onTimeChanged: (value) {
params.visitTime = value;
},
),
BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
builder: (context, state) {
if (state is DailyCallCardLoaded) {
return Column(
children: [
Visibility(
visible: state.showTerritory ?? false,
child: CustomDropdownSearch(
labelText: 'Select Territory',
items: (f, s) => state.territoryList,
itemAsString: (Territory data) => '${data.territoryName}\n${data.upperTerritoryCode}',
onChanged: (value) {
setState(() {
selectedTerritory = value;
});
context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorListByTerritoryEvent(territoryCode: value!.upperTerritoryCode));
},
),
),
state.isDoctorLoading!
? const Center(child: ThreeDotLoading())
: CustomDropdownSearch(
labelText: 'Select Doctor',
items: (f, s) => state.doctorList,
itemAsString: (Doctor data) => data.name!,
onChanged: (value) {
params.doctorID = value!.doctorId;
},
),
CustomTextFormField(
hintText: 'Write a comment here....',
maxLines: 2,
onChanged: (value) {
params.opinion = value;
},
),
state.isSettingPlan!
? const ThreeDotLoading()
: CustomButton(
title: 'Set Plan',
onPressed: () {
if (callCardKey.currentState!.validate()) {
context
.read<DoctorDailyCallCardBloc>()
.add(SetCallCard(params: params));
}
},
),
],
);
}
return const SizedBox.shrink();
},
),
],
),
),
],
),
);
}
}
Upvotes: 0