3

enter image description here

For a Dashboard design as shown in image, I've to display various counts fetched from API. Each count will be fetched from different API by looking at the label.

My developed Structure,

  • DashboardScreen
    • CustomBottomMenuOptionWidget(label) - StatelessWidget
      • MenuCountPage(label) - StatefulWidget (to display count, I've taken separate MenuCountScreen widget with its own event, state & bloc as per label)

When application get open everything works fine. For each different menu option I'm able to get counts for each label. My main problem is user move forward in app and create a new event and when come back to dashboard, how can I refresh this counts or simply says how can I add event to BLoC of MenuCountScreen to get updated value?.

Current implementation:

dashboard.dart (GridView)

GridView.count(
            primary: false,
            padding: const EdgeInsets.all(20),
            crossAxisSpacing: 20,
            mainAxisSpacing: 20,
            crossAxisCount: 2,
            childAspectRatio: 1.3,
            children: <Widget>[
              HomeBottomGridMenuItem(
                label: kMenuLabelCalendar,
                onItemClick: () {
                  _onTapHomeMenuItem(context, kMenuLabelCalendar);
                },
                icon: ICON_CALENDAR,
                itemCountExist: true,
                itemCount: 10,
              ),
              ...other menu item
)

HomeBottomGridMenuItem.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/resources/colors.dart';
import 'package:flutter_app/screens/dashboard/menu_count/menu_count.dart';

class HomeBottomGridMenuItem extends StatelessWidget {
  final String label;
  final String icon;
  final Function onItemClick;
  final bool itemCountExist;
  final int itemCount;

  HomeBottomGridMenuItem({
    this.label,
    this.icon,
    this.onItemClick,
    this.itemCountExist,
    this.itemCount,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: <Widget>[
        InkWell(
          onTap: onItemClick,
          splashColor: Colors.black26,
          child: Container(
            padding: EdgeInsets.symmetric(
              horizontal: 16.0,
              vertical: 8.0,
            ),
            decoration: BoxDecoration(
              border: Border.all(
                color: Colors.grey,
              ),
              borderRadius: BorderRadius.all(
                Radius.circular(
                  8.0,
                ),
              ),
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Image.asset(
                  icon,
                  height: 40.0,
                  width: 40.0,
                  color: kColorDashboardMenuItemIcon,
                ),
                Text(
                  label,
                  textAlign: TextAlign.start,
                  style: Theme.of(context).textTheme.headline4.copyWith(
                        fontWeight: FontWeight.w400,
                        color: kColorDashboardMenuItemLabel,
                      ),
                )
              ],
            ),
          ),
        ),
        Positioned(
          top: 0,
          right: 0,
          child: Visibility(
            visible: itemCountExist,
            child: Container(
              padding: EdgeInsets.symmetric(
                horizontal: 14.0,
                vertical: 5.0,
              ),
              decoration: BoxDecoration(
                color: kColorAppPrimaryBlackShade,
                borderRadius: BorderRadius.only(
                  topRight: Radius.circular(
                    5.0,
                  ),
                  bottomLeft: Radius.circular(
                    5.0,
                  ),
                ),
              ),
              child: MenuCountPage(
                label: label,
              ),
            ),
          ),
        ),
      ],
    );
  }
}

MenuCountPage.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'menu_count.dart';

class MenuCountPage extends StatelessWidget {
  final String label;

  MenuCountPage({
    @required this.label,
  });

  @override
  Widget build(BuildContext context) {
    return BlocProvider<MenuCountBloc>(
      create: (context) {
        return MenuCountBloc(context: context);
      },
      child: MenuCountScreen(
        menuLabel: label,
      ),
    );
  }
}

MenuCountScreen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_app/widgets/loader_circular.dart';

import 'menu_count.dart';

class MenuCountScreen extends StatefulWidget {
  final String menuLabel;

  MenuCountScreen({
    @required this.menuLabel,
  });

  @override
  _MenuCountScreenState createState() => _MenuCountScreenState();
}

class _MenuCountScreenState extends State<MenuCountScreen> {
  MenuCountBloc _menuCountBloc;

  @override
  void initState() {
    _menuCountBloc = BlocProvider.of<MenuCountBloc>(context);

    _menuCountBloc.add(
      GetMenuCount(menuLabel: widget.menuLabel),
    );

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: BlocBuilder<MenuCountBloc, MenuCountState>(
        builder: (context, state) {
          if (state is MenuCountSuccess) {
            return Text(
              '${state.count}',
              style: Theme.of(context).textTheme.headline2.copyWith(
                    color: Colors.white,
                  ),
            );
          }
          if (state is MenuCountFail) {
            return Text(
              '0',
              style: Theme.of(context).textTheme.headline2.copyWith(
                    color: Colors.white,
                  ),
            );
          }
          return CircularLoader(
            size: 25,
            strokeWidth: 3,
            color: Colors.white60,
          );
        },
      ),
    );
  }
}

MenuCountEvent.dart

import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';

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

class GetMenuCount extends MenuCountEvent {
  final String menuLabel;

  GetMenuCount({@required this.menuLabel});

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

MenuCountState.dart

import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';

abstract class MenuCountState extends Equatable {
  const MenuCountState();

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

class MenuCountInitial extends MenuCountState {}

class MenuCountLoading extends MenuCountState {}

class MenuCountSuccess extends MenuCountState {
  final int count;

  MenuCountSuccess({@required this.count});

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

class MenuCountFail extends MenuCountState {}

MenuCountBloc.dart

import 'package:chopper/chopper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_app/api/api_service.dart';
import 'package:flutter_app/models/dashboard_count_responses/events_count_response.dart';
import 'package:flutter_app/models/dashboard_count_responses/open_ticket_count_response.dart';
import 'package:flutter_app/models/dashboard_count_responses/properties_count_response.dart';
import 'package:flutter_app/resources/strings.dart';
import 'package:flutter_app/utility/sharedpref_helper.dart';
import 'package:provider/provider.dart';

import 'menu_count.dart';

class MenuCountBloc extends Bloc<MenuCountEvent, MenuCountState> {
  final BuildContext context;

  MenuCountBloc({@required this.context});

  @override
  MenuCountState get initialState => MenuCountInitial();

  @override
  Stream<MenuCountState> mapEventToState(MenuCountEvent event) async* {
    if (event is GetMenuCount) {
      yield MenuCountLoading();
      if (event.menuLabel.length > 0) {
        yield* _getCountValueByLabel(event.menuLabel);
      }
    }
  }

  Stream<MenuCountState> _getCountValueByLabel(String menuLabel) async* {
    switch (menuLabel) {
      case kMenuLabelOpenTickets:
        try {
          final String token = await SharedPreferenceHelper.getToken();
          final Response<OpenTicketCountResponse> apiResponse =
              await Provider.of<ApiService>(context, listen: false)
                  .getOpenTicketCount(token);
          if (apiResponse.isSuccessful) {
            yield MenuCountSuccess(count: apiResponse.body.openTickets);
          } else {
            print('API : Response Failed');
            yield MenuCountSuccess(count: 0);
          }
        } catch (exception) {
          print('Exception: ${exception.toString()}');
          yield MenuCountSuccess(count: 0);
        }
        break;
      case kMenuLabelProperties:
        try {
          final String token = await SharedPreferenceHelper.getToken();
          final Response<PropertiesCountResponse> apiResponse =
              await Provider.of<ApiService>(context, listen: false)
                  .getPropertiesCount(token);
          if (apiResponse.isSuccessful) {
            yield MenuCountSuccess(count: apiResponse.body.properties_active);
          } else {
            print('API : Response Failed');
            yield MenuCountSuccess(count: 0);
          }
        } catch (exception) {
          print('Exception: ${exception.toString()}');
          yield MenuCountSuccess(count: 0);
        }
        break;
      case kMenuLabelCalendar:
        try {
          final String token = await SharedPreferenceHelper.getToken();
          final Response<EventsCountResponse> apiResponse =
              await Provider.of<ApiService>(context, listen: false)
                  .getEventsCount(token);
          if (apiResponse.isSuccessful) {
            yield MenuCountSuccess(count: apiResponse.body.events);
          } else {
            print('API : Response Failed');
            yield MenuCountSuccess(count: 0);
          }
        } catch (exception) {
          print('Exception: ${exception.toString()}');
          yield MenuCountSuccess(count: 0);
        }
        break;
      case kMenuLabelAllTickets:
        try {
          final String token = await SharedPreferenceHelper.getToken();
          final Response<OpenTicketCountResponse> apiResponse =
              await Provider.of<ApiService>(context, listen: false)
                  .getOpenTicketCount(token);
          if (apiResponse.isSuccessful) {
            yield MenuCountSuccess(count: apiResponse.body.tickets);
          } else {
            print('API : Response Failed');
            yield MenuCountSuccess(count: 0);
          }
        } catch (exception) {
          print('Exception: ${exception.toString()}');
          yield MenuCountSuccess(count: 0);
        }
        break;
      default:
        yield MenuCountSuccess(count: 0);
    }
  }
}

Things that I already tried:

  1. Provided BLoC from Parent (from 'Dashboard') and when user come back to dashboard tried adding event to get updated count. Didn't work. (If it's because I've different API call based on label & each count is associated with it's own instance of MenuCountBloc - if I'm wrong please clear me out)

  2. Tried taking a bool value and pass it to MenuCountScreen from Dashboard and when user come back to dashboard update the value of that bool thinking that it'll refresh and call event again but didn't worked.

  3. Addition to tried option 1, take 4 different int parameters to store 4 different count in BLoC as well as in MenuCountState. Thought it'll store 4 values for BLoC that I've provided from Dashboard. But didn't worked out.

    I would like to know, my way of implementation is correct or not and things that I've tried. Also a possible solution to achieve my task where I'm stuck.

EDITED : Sample Project I pushed here : GitHub

Community
  • 1
  • 1
Purvik Rana
  • 331
  • 7
  • 17

1 Answers1

0

So basically you are saying ' when you come back to your old screen then how to updated values? '

It's is not related to BLoC but it is related to Navigators.

if you notice you will see that each Navigator.push function returns a future. Basically you can await till you are on the next screen.

When you pop from the next screen you can come to the first screen and your Navigator.push will tell you that the next screen is closed.

Let's see some code.

class _AState extends State<A> {
  void gotoB() async {
    await Navigator.push(context, MaterialPageRoute(builder: (context) => B()));
    refreshScreen();
  }

  void refreshScreen() {
    // Call BLoC functions to update the screen
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class B extends StatefulWidget {
  @override
  _BState createState() => _BState();
}

class _BState extends State<B> {
  completeWork() {
    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Here from class A I am going to class B. And I am using await in front of Navigator to wait till I come back from B.

From B when I am doing Navigator.pop() I am coming back to A. After coming here I am refreshing my screen A.

vivek yadav
  • 1,367
  • 2
  • 12
  • 16
  • Thanks for your answer but this is not in my case. I have already followed (scenario that is in your answer) for one of screen where I'm refreshing list fetched from API. If possible please check git repo to see what I want to achieve. – Purvik Rana May 21 '20 at 16:44