flutterdartblocflutter-blocflutter-gridview

TextButton inside a BlocBuilder getting disabled by default in Flutter


I am trying to display a basic screen via flutter Bloc library , where in a person can view the number of appointments he had for a particular day , by clicking the particular date in a Calendar widget .

Below is the code of the above mentioned screen

import 'dart:developer';

import 'package:chikitsalaya/doctor/bloc/doctor_appointment_bloc.dart';
import 'package:chikitsalaya/doctor/bloc/doctor_appointment_event.dart';
import 'package:chikitsalaya/doctor/bloc/doctor_appointment_state.dart';
import 'package:chikitsalaya/doctor/doctor_appointment_details_screen.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';

import '../common/common_ui.dart';
import '../payment/appointment_booking_payment.dart';

class DoctorAppointmentScreen extends StatelessWidget {

  const DoctorAppointmentScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => DoctorAppointmentBloc(DoctorAppointmentInitial()),
      child: Scaffold(
          backgroundColor: Colors.white,
          appBar: CommonUI(context).commonAppBar(),
          drawer: CommonUI(context).commonDrawer(),
          body: SafeArea(
            minimum: const EdgeInsets.all(16.0),
            child: Column(
              children: <Widget>[
                /// This is the Calendar Picker Widget , where doctor can pick a particular date and see what all appointments are present
                buildCalendarWidget(context),
                /// This is the BlocBuilder Widget for dynamically displaying all the appointments for a particular date picked above
                BlocBuilder<DoctorAppointmentBloc,DoctorAppointmentState>(
                    builder: (context,state){
                      if(state is DoctorAppointmentDatePicked){
                        List<dynamic> currentDayAppointmentDetails = state.appointmentDetails;
                        return buildDailyAppointmentWidget(context,currentDayAppointmentDetails,state.day);
                      }else if(state is DoctorAppointmentInitial){
                        /// This is the initial state , so the date naturally would be the current date
                        context.read<DoctorAppointmentBloc>().add(DoctorAppointmentScreenOnDateChanged(DateTime.now()));
                        /// This is required because BlocBuilder requires a mandatory Widget to be returned back
                        return const CircularProgressIndicator();
                      }else if(state is DoctorAppointmentScreenError){
                        log("Error from Backend "+state.exceptionMessage);
                        return ErrorWidget(state.exceptionMessage);
                      }else if(state is DoctorAppointmentScreenNoAppointmentFound){
                        return Text("No Appointment Found for "+state.pickedDate);
                      }else if(state is DoctorAppointmentDetailsScreenInitial){
                        log("Inside state DoctorAppointmentDetailsScreenInitial");
                        //TODO: User View Resolver for this transition
                        Navigator.push(context, MaterialPageRoute(builder: (context) => DoctorAppointmentDetailsScreen(appointmentId:state.appointmentId)));
                        return const CircularProgressIndicator();
                      }else{
                        return const CircularProgressIndicator();
                      }
                    }
                ),
              ],
            ),
          )
      ),
    );
  }

  Widget buildCalendarWidget(BuildContext context) {
    return BlocBuilder<DoctorAppointmentBloc,DoctorAppointmentState>(
      builder: (context,state) {
        return CalendarDatePicker(
            initialDate: DateTime.now(),
            firstDate: DateTime.now(),
            lastDate: DateTime(2024),
            onDateChanged: (dateTime) {
              log('DateTime picked is '+dateTime.toString());
              context.read<DoctorAppointmentBloc>().add(DoctorAppointmentScreenOnDateChanged(dateTime));
            });
      }
    );
  }

  Card buildDailyAppointmentWidget(BuildContext context,List<dynamic> currentDayAppointmentDetails, String day){
    return Card(
      elevation: 6,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      child: Column(
        children: <Widget>[
          Text(day),
          Expanded(
            child: GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: (currentDayAppointmentDetails.length>=3) ? 3 : currentDayAppointmentDetails.length
              ),
              itemCount: currentDayAppointmentDetails.length,
              shrinkWrap: true,
              itemBuilder: (context,index) {
                return Padding(
                      padding: const EdgeInsets.only(left: 10.0,top: 40.0,right: 10.0,bottom: 40.0),
                      child: BlocBuilder<DoctorAppointmentBloc,DoctorAppointmentState>(
                        builder: (context,state) {
                          return TextButton(
                            /*style: ElevatedButton.styleFrom(
                              primary: Colors.green,
                              onPrimary: Colors.white,
                              shadowColor: Colors.greenAccent,
                              elevation: 3,
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(32.0)),
                            ),*/
                            onPressed: () {
                              try{
                                log("Pressed button");
                                print("Pressed someting");
                                context.read<DoctorAppointmentBloc>().add(DoctorAppointmentScreenOnPressed(currentDayAppointmentDetails[index]['appointmentId']));
                              }on Exception catch (_,error){
                                log("Error is "+error.toString());
                              }
                            },
                            //label: Text(currentDayAppointmentDetails![index]!['time']!),
                            //backgroundColor: Colors.green,
                            child: Text(currentDayAppointmentDetails![index]!['time']!),
                          );
                        }
                      ),
                    );
              }),
            ),
        ],
      ),
    );
  }

}

The below code is running fine , and even the appointmentList items are getting presented properly in a GridView . But on Pressing any individual appointment date, the "onPressed" function is not getting called . And it even includes the "print" and "log" lines .

onPressed: () {
                              try{
                                log("Pressed button");
                                print("Pressed someting");
                                context.read<DoctorAppointmentBloc>().add(DoctorAppointmentScreenOnPressed(currentDayAppointmentDetails[index]['appointmentId']));
                              }on Exception catch (_,error){
                                log("Error is "+error.toString());
                              }
                            },

I have also tried using "ElevatedButton & FloatingActionButton" instead of the existing "TextButton" ,but nothing seems to be working . And it seems as if buttons are getting disabled by default.

I am really feeling stuck over here , and any help would be appreciated .

I am also providing the Bloc , BlocState and BlocEvent codes

Bloc

import 'dart:async';
import 'dart:developer';
import 'package:chikitsalaya/appointment/appointment_service.dart';
import 'package:chikitsalaya/common/user_service.dart';
import 'package:chikitsalaya/doctor/bloc/doctor_appointment_event.dart';
import 'package:chikitsalaya/doctor/bloc/doctor_appointment_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';

class DoctorAppointmentBloc extends Bloc<DoctorAppointmentEvent,DoctorAppointmentState>{

  final AppointmentService _appointmentService = AppointmentServiceImpl();
  final UserService _userService = UserServiceImpl();

  DoctorAppointmentBloc(DoctorAppointmentState initialState) : super(initialState){
    on<DoctorAppointmentScreenOnDateChanged>(_onDateChanged);
    on<DoctorAppointmentDetailsScreenOnInit>(_onInitDetailsScreen);
    on<DoctorAppointmentScreenOnPressed>(_onPressed);
  }

  /// OnDateChanged , need to fetch all the monthly appointments from backend
  FutureOr<void> _onDateChanged(event,emit) async{
    DateTime pickedDate = event.dateTime;
    //TODO: Get the month of the pickedDate and pass it as a parameter for fetching data
    String formattedDate = DateFormat('dd-MM-yyyy').format(pickedDate);
    _appointmentService.fetchMonthlyAppointments("doctorId", "month")
        .then((appointmentList) {
          bool found = false;
          for(int i=0;i<appointmentList.length;i++){
            if(appointmentList[i].date==formattedDate){
              found = true;
              String weekDay = DateFormat('EEEE').format(pickedDate);
              emit(DoctorAppointmentDatePicked(appointmentDetails: appointmentList[i].appointments,
                  day: weekDay));
            }
            if(!found){
              log("No appointment found for date "+formattedDate);
              emit(DoctorAppointmentScreenNoAppointmentFound(formattedDate));
            }
          }
        }).onError((error, stackTrace) {
          log("In DoctorAppointmentScreenBloc error "+error.toString());
          log("In DoctorAppointmentScreenBloc stacktrace "+stackTrace.toString());
          emit(DoctorAppointmentScreenError(error.toString()));
        });
  }


  FutureOr<void> _onInitDetailsScreen(event, emit) async{
    String appointmentId = (event as DoctorAppointmentDetailsScreenOnInit).appointmentId;
    _appointmentService.fetchAppointmentDetails(appointmentId)
        .then((appointment) {
          log("Appointment Found for Id "+appointmentId+" "+appointment.toString());
          /// After fetching appointment details , need to fetch Patient details via Patient API
          String patientId = appointment.patientId;
          _userService.fetchPatientDetails(patientId)
              .then((patient) {
                log("Patient details found for Id "+patientId+" "+patient.toString());
                appointment.patientName=patient.patientName;
                appointment.patientAge=patient.patientAge;
                appointment.patientSex=patient.patientSex;
                emit(DoctorAppointmentDetailsScreenSuccess(appointment));
              }).onError((error, stackTrace) {
                log("Failed to fetch Patient details for Id "+patientId+" "+stackTrace.toString());
                emit(DoctorAppointmentDetailsScreenError(error.toString()));
              });
        }).onError((error, stackTrace) {
          log("Failed to fetch Appointment details for Id "+appointmentId+" "+stackTrace.toString());
          emit(DoctorAppointmentDetailsScreenError(error.toString()));
        });
  }

  FutureOr<void> _onPressed(event, emit) async{
    String appointmentId = (event as DoctorAppointmentScreenOnPressed).appointmentId;
    log("Inside AppointmentBloc OnPressed "+appointmentId);
    emit(DoctorAppointmentDetailsScreenInitial(appointmentId));
  }
}

BlocEvent

import 'package:equatable/equatable.dart';

abstract class DoctorAppointmentEvent extends Equatable {
  const DoctorAppointmentEvent();
}

class DoctorAppointmentStarted extends DoctorAppointmentEvent {
  @override
  List<Object?> get props => [];

}

class DoctorAppointmentScreenOnDateChanged extends DoctorAppointmentEvent {
  final DateTime dateTime;
  //TODO: Add Doctor ID as variable here

  const DoctorAppointmentScreenOnDateChanged(this.dateTime);

  @override
  List<Object?> get props => [dateTime];

}

class DoctorAppointmentScreenOnPressed extends DoctorAppointmentEvent {
  final String appointmentId;

  const DoctorAppointmentScreenOnPressed(this.appointmentId);

  @override
  List<Object?> get props => [appointmentId];

}

class DoctorAppointmentDetailsScreenOnInit extends DoctorAppointmentEvent {
  final String appointmentId;

  const DoctorAppointmentDetailsScreenOnInit(this.appointmentId);

  @override
  List<Object?> get props => [];

}

BlocState

import 'package:chikitsalaya/appointment/appointment_model.dart';
import 'package:equatable/equatable.dart';

abstract class DoctorAppointmentState extends Equatable {
  const DoctorAppointmentState();
}

class DoctorAppointmentInitial extends DoctorAppointmentState {

  const DoctorAppointmentInitial();

  @override
  List<Object?> get props => [];
}

class DoctorAppointmentDatePicked extends DoctorAppointmentState {

  final List<dynamic> appointmentDetails;
  final String day;
  const DoctorAppointmentDatePicked({required this.appointmentDetails,required this.day});

  @override
  List<Object?> get props => [appointmentDetails,day];

}

class DoctorAppointmentScreenError extends DoctorAppointmentState {
  final String exceptionMessage;

  const DoctorAppointmentScreenError(this.exceptionMessage);

  @override
  List<Object?> get props => [exceptionMessage];
}

class DoctorAppointmentScreenNoAppointmentFound extends DoctorAppointmentState {
  final String pickedDate;

  const DoctorAppointmentScreenNoAppointmentFound(this.pickedDate);

  @override
  List<Object?> get props => [pickedDate];
}

class DoctorAppointmentDetailsScreenInitial extends DoctorAppointmentState {
  final String appointmentId;

  const DoctorAppointmentDetailsScreenInitial(this.appointmentId);

  @override
  List<Object?> get props => [appointmentId];

}

class DoctorAppointmentDetailsScreenSuccess extends DoctorAppointmentState {
  final Appointment appointment;

  const DoctorAppointmentDetailsScreenSuccess(this.appointment);

  @override
  List<Object?> get props => [appointment];
}

class DoctorAppointmentDetailsScreenError extends DoctorAppointmentState {
  final String exceptionMessage;

  const DoctorAppointmentDetailsScreenError(this.exceptionMessage);

  @override
  List<Object?> get props => [exceptionMessage];
}

Solution

  • It seems you are not allowed to add a clickable card inside a parent card .
    So , the moment i changed my parent widget to return a Column instead of a card , it worked .

    So , in short
    Old Code

    return Card(
      elevation: 6,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      child: Column(
    

    Working Code

    return Column(
        children: <Widget>[
          Text(day),
          Expanded(