在构建上下文中显示小吃栏时,查找未激活的窗口小部件

时间:2019-10-28 20:31:03

标签: flutter flutter-redux

我有一个屏幕,其中绘制了多个称为EventCard的子窗口小部件。每张卡都有一个可单击的切换按钮,它将分派一个redux操作来触发HTTP调用,以便使用选项之一(切换按钮)更新数据库中的模型。

http调用完成后,我们检查API的响应,如果一切正常,我们希望在屏幕上显示小吃栏。

当我们使用Scaffold.of(buildContext).showSnackBar()函数时会发生问题,因为buildContext已经被卸除,所以我得到了错误:

  

查找已停用的窗口小部件的祖先是不安全的。   此时,小部件的元素树的状态不再稳定。   为了在其dispose()方法中安全地引用窗口小部件的祖先,请通过在窗口小部件的didChangeDependencies()方法中调用InheritFromWidgetOfExactType()来保存对祖先的引用。)

这是我的屏幕组件的构建功能:

@override


Widget build(BuildContext context) {
    return StoreConnector<AppState, EventScreenModel>(
        model: EventScreenModel(),
        builder: (BuildContext context, EventScreenModel model) {
          final int childCount = model.events[model.currentRoleId] != null
              ? model.events[model.currentRoleId].length
              : 0;
          return Scaffold(
            appBar: CustomAppBar(),
            body: model.isLoading
                ? Center(
              child: PulsingLogo(),
            )
                : Scrollbar(
              child: CustomScrollView(
                slivers: <Widget>[
                  SliverToBoxAdapter(
                    child: Padding(
                      padding: const EdgeInsets.only(
                          top: 24.0, left: 24.0, bottom: 10.0),
                      child: Text(
                        'Moje udalosti',
                        style: Theme.of(context).textTheme.headline,
                      ),
                    ),
                  ),
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                            (BuildContext context, int index) {
                          Event.Event event =
                          model.events[model.currentRoleId][index];
                          String role;
                          model.roles.forEach((i, acc) {
                            acc.forEach((listOfRoles) {
                              if (listOfRoles.academy_id == event.academy_id) {
                                role = listOfRoles.role;
                              }
                            });
                          });
                          return Padding(
                            padding:
                            const EdgeInsets.symmetric(horizontal: 16.0),
                            child: EventCard(
                              event: event,
                              currentRoleId: model.currentRoleId,
                              role: role,
                            ),
                          );
                        }, childCount: childCount),
                  ),
                  if (childCount == 0)
                    SliverToBoxAdapter(
                      child: Center(
                        child: Padding(
                          padding: const EdgeInsets.all(36.0),
                          child: Text('Nenašli sa žiadne udalosti.'),
                        ),
                      ),
                    ),
                  SliverToBoxAdapter(
                    child: FractionallySizedBox(
                      widthFactor: 0.8,
                      child: RaisedButton(
                        onPressed: () {},
                        child: Text('Načítať dalšie'),
                      ),
                    ),
                  )
                ],
              ),
            ),
            drawer: Menu(),
          );
        }
    );
  }

这是子EventCard组件:

import 'package:academy_app/components/switch_button.dart';
import 'package:academy_app/main.dart';
import 'package:academy_app/screens/events_screen.dart';
import 'package:academy_app/state/event_state.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:academy_app/models/event.dart';

class EventCard extends StatelessWidget {
  const EventCard(
      {Key key,
      @required this.event,
      this.showAttendanceButton = true,
      this.role,
      this.currentRoleId})
      : super(key: key);

  final Event event;
  final bool showAttendanceButton;
  final String role;
  final int currentRoleId;

  bool get isGoing {
    if (role != null && role != 'player')
      return event.coaches != null
          ? event.coaches
              .firstWhere((coach) => coach.id == currentRoleId)
              .pivot
              .participate
          : false;
    return event.players != null
        ? event.players
            .firstWhere((player) => player.id == currentRoleId)
            .pivot
            .participate
        : false;
  }

  _changeAttendance(context) {
    store.dispatch(
      SetEventAttendanceAction(
          eventId: event.id,
          isGoing: !isGoing,
          buildContext: context,
          roleId: currentRoleId),
    );
  }

  String getDay(String date) => DateTime.parse(date).day.toString();

  String getMonth(String date) =>
      DateFormat('MMM').format(DateTime.parse(date)).toUpperCase();

  String getTime(String date) =>
      DateFormat('HH:mm').format(DateTime.parse(date));

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        if (event != null)
          Card(
            child: ListTile(
              title: Text(
                event.name,
                style: Theme.of(context).textTheme.title,
              ),
              subtitle: Text(
                getTime(event.start_time) + '-' + getTime(event.end_time),
              ),
              leading: Container(
                margin: EdgeInsets.all(4.0),
                padding: EdgeInsets.all(5.0),
                decoration: BoxDecoration(
                  color: Colors.orange[400],
                  borderRadius: BorderRadius.circular(4.0),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      getDay(event.start_time),
                      style: TextStyle(color: Colors.white),
                    ),
                    Text(
                      getMonth(event.end_time),
                      style: TextStyle(color: Colors.white),
                    ),
                  ],
                ),
              ),
              trailing: AttendanceSwitchButton(
                onPressed: () => _changeAttendance(context),
                isGoing: isGoing,
                small: true,
              ),
            ),
          ),
      ],
    );
  }
}

最后,在点击时调用的redux动作,也就是引发错误的动作:

import 'dart:convert';

import 'package:academy_app/constants.dart';
import 'package:academy_app/models/event.dart';
import 'package:academy_app/models/role.dart';
import 'package:academy_app/models/serializers.dart';
import 'package:academy_app/state/roles_state.dart';
import 'package:academy_app/state/ui_state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'app_state.dart';

class GetEventsAction extends BarrierAction {
  final int roleId;

  GetEventsAction({
    @required this.roleId,
  });

  @override
  Future<AppState> reduce() async {
    try {
      final Role role = RolesState.rolesMap(state)[roleId];
      if (role == null) {
        return state;
      }
      final response = await http.get(
          UrlBuilder.uri('v1/events/${role.role}/${role.id}'),
          headers: UrlBuilder.headers);

      if (response.statusCode != 200) {
        print(response.body);
        return state;
      }

      List<Event> events =
          List.from(json.decode(response.body)).map((dynamic value) {
        return serializers.deserializeWith(Event.serializer, value);
      }).toList();

      events.sort((a, b) {
        return DateTime.parse(b.updated_at)
            .compareTo(DateTime.parse(a.updated_at));
      });

      return state.copy(events: {
        ...state.events,
        role.id: events,
      });
    } catch (error) {
      throw Exception('Failed to get trainings data from server: $error');
    }
  }
}

class SetEventAttendanceAction extends BarrierAction {
  final int eventId;
  final bool isGoing;
  final BuildContext buildContext;
  final int roleId;

  SetEventAttendanceAction(
      {@required this.eventId,
      @required this.isGoing,
      @required this.buildContext,
      @required this.roleId});

  @override
  Future<AppState> reduce() async {
    try {
      final Role role = RolesState.rolesMap(state)[roleId];
      final response = await http.post(
          UrlBuilder.uri(isGoing
              ? 'v1/event/$eventId/${role.role}/$roleId/going'
              : 'v1/event/$eventId/${role.role}/$roleId/not-going'),
          headers: UrlBuilder.headers);

      if (response.statusCode != 200) {
        return state;
      }

      if (response.body == 'false') {
        Scaffold.of(buildContext).showSnackBar(
          SnackBar(
            content: Text(
              isGoing
                  ? 'Nepodarilo sa prihlásiť na udalosť.'
                  : 'Nepodarilo sa odhlásiť z udalosti.',
              //style: Theme.of(buildContext).textTheme.subtitle,
            ),
          ),
        );
      } else if (response.body == 'true') {
        // store.dispatch(GetEventsAction(roleId: state.currentRoleId));
        Scaffold.of(buildContext).showSnackBar(
          SnackBar(
            content: Text(
              isGoing
                  ? 'Boli ste prihlásený na udalosť.'
                  : 'Boli ste odhlásený z udalosti.',
              //style: Theme.of(buildContext).textTheme.subtitle,
            ),
          ),
        );
      } else {
        return state;
      }
    } catch (error) {
      throw Exception('Failed to get training data from server: $error');
    }
  }
}

有什么想法吗?

1 个答案:

答案 0 :(得分:0)

您现在可能已经找到了解决方案,但这里是:

我的用例与您的非常相似。我设法通过这样做来解决它:

(1) 创建一个可以被其他小部件访问的全局类(globals.dart)-

import 'package:flutter/material.dart';

class Globals {
  static BuildContext currentContext;
}

(2) 将 globals.dart 导入您希望在其上显示小吃栏的每个小部件

(3) 在widget的build函数中,让全局对象知道哪个是当前活动的上下文:

Widget build(BuildContext context) {
    Globals.currentContext = context;

... truncated

}

(4) 在您希望显示小吃栏的小部件中,使用 Globals.currentContext 作为构建上下文。在您的情况下,它应该是:

Scaffold.of(Globals.currentContext).showSnackBar

这是我对 SO 的第一个贡献。希望这对某人有帮助:)