尽管观察者的状态发生变化,为什么 Flutter BloC UI 在应用程序重启后没有更新?

Why is Flutter BloC UI not updating after app restart despite state change in observer?

我承认有很多类似我的问题,但我没有从这些问题中找到满意的答案。所以我决定提出自己的问题来说明我的问题。我的程序中有 3 个 BloC,每个都有不同的目的。他们都有相似的问题,因此我将询问其中一个 BloC,希望一个解决方案能够解决所有 BloC。

问题是这样的,如果我刚刚启动应用程序并登录,BloC 将更新 UI。如果我登录、退出应用程序并重新启动它,Bloc 将不会更新 UI。所讨论的 Bloc 称为 DetailpersonilBloc,其中 1 个事件称为 Detail,2 个状态称为 DetailpersonilInitial 和 Loaded。在 Detail 事件中,应该发出 Loaded 状态。

我在 LoginPage 调用了 Detail,在 initState 的 GajiPage 调用了 Detail。这在我刚打开应用程序时有效,但在我重新启动应用程序时不起作用。我也有平等的想法,它会帮助我,但显然它没有改变。

注意:GajiPage 中的“...”只是一些我认为不需要复制的代码。

DetailpersonilBloc

part 'detailpersonil_event.dart';
part 'detailpersonil_state.dart';

class DetailpersonilBloc
    extends Bloc<DetailpersonilEvent, DetailpersonilState> {
  DetailpersonilBloc() : super(const DetailpersonilInitial()) {
    on<Detail>((event, emit) async {
      SharedPreferences pref = await SharedPreferences.getInstance();
      String name = pref.getString('nama');
      String nrp = pref.getString('NRP');
      String pangkat = pref.getString('pangkat');
      String jabatan = pref.getString('jabatan');
      String satker = pref.getString('satker');
      String polda = pref.getString('polda');
      String npwp = pref.getString('NPWP');
      String rekening = pref.getString('rekening');
      String bank = pref.getString('bank');
      emit(Loaded(
        name,
        nrp,
        pangkat,
        jabatan,
        satker,
        polda,
        npwp,
        rekening,
        bank,
      ));
    });
  }
}

DetailpersonilEvent

part of 'detailpersonil_bloc.dart';

@immutable
abstract class DetailpersonilEvent extends Equatable {}

class Detail extends DetailpersonilEvent {
  @override
  List<Object> get props => [];
}

DetailpersonilState

part of 'detailpersonil_bloc.dart';

@immutable
abstract class DetailpersonilState extends Equatable {
  final String nama;
  final String nrp;
  final String pangkat;
  final String jabatan;
  final String satker;
  final String polda;
  final String npwp;
  final String rekening;
  final String bank;

  const DetailpersonilState(
      {this.nama,
      this.nrp,
      this.pangkat,
      this.jabatan,
      this.satker,
      this.polda,
      this.npwp,
      this.rekening,
      this.bank});
}

class DetailpersonilInitial extends DetailpersonilState {
  const DetailpersonilInitial()
      : super(
          nama: 'Nama',
          nrp: 'NRP',
          pangkat: 'Pangkat',
          jabatan: 'Jabatan',
          satker: 'Satker',
          polda: 'Polda',
          npwp: 'NPWP',
          rekening: 'No Rekening',
          bank: 'Nama Bank',
        );

  @override
  List<Object> get props =>
      [nama, nrp, pangkat, jabatan, satker, polda, npwp, rekening, bank];
}

class Loaded extends DetailpersonilState {
  const Loaded(
    String nama,
    String nrp,
    String pangkat,
    String jabatan,
    String satker,
    String polda,
    String npwp,
    String rekening,
    String bank,
  ) : super(
            nama: nama,
            nrp: nrp,
            pangkat: pangkat,
            jabatan: jabatan,
            satker: satker,
            polda: polda,
            npwp: npwp,
            rekening: rekening,
            bank: bank);

  @override
  List<Object> get props =>
      [nama, nrp, pangkat, jabatan, satker, polda, npwp, rekening, bank];
}

登录页面

class LoginPage extends StatefulWidget {
  const LoginPage({Key key}) : super(key: key);

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

class _LoginPageState extends State<LoginPage> {
  double width = 0;
  double height = 0;
  TextEditingController nrpController = TextEditingController();
  TextEditingController sandiController = TextEditingController();
  final formKey = GlobalKey<FormState>();
  DetailpersonilBloc detailPersonilBloc;
  GajiBloc gajiBloc;
  TunkinBloc tunkinBloc;
  bool passwordVisible = false;

  @override
  void initState() {
    super.initState();
    checkToken();
  }

  void checkToken() async {
    SharedPreferences pref = await SharedPreferences.getInstance();
    if (pref.getString('token') != null) {
      detailPersonilBloc = DetailpersonilBloc();
      gajiBloc = GajiBloc();
      tunkinBloc = TunkinBloc();
      detailPersonilBloc.add(Detail());
      gajiBloc.add(Gaji());
      tunkinBloc.add(Tunkin());
      Navigator.push(
          context, MaterialPageRoute(builder: (context) => const MainPage()));
    }
  }

  onLogin(DetailpersonilBloc detailPersonilBloc) async {
    if (formKey.currentState.validate()) {
      var token = await APIService.generateToken(
          nrpController.text, sandiController.text);
      if (token != null) {
        SharedPreferences pref = await SharedPreferences.getInstance();
        await pref.setString('token', token.token);
        var detail = await APIService.getDetailPersonil(token.token);
        await pref.setString('nama', detail.nMPEG);
        await pref.setString('NRP', detail.nRP);
        await pref.setString('pangkat', detail.nMGOL1);
        await pref.setString('jabatan', detail.sEBUTJAB);
        await pref.setString('satker', detail.nMSATKER);
        await pref.setString('polda', detail.nMUAPPAW);
        await pref.setString('NPWP', detail.nPWP);
        await pref.setString('rekening', detail.rEKENING);
        await pref.setString('bank', detail.nMBANK);
        nrpController.clear();
        sandiController.clear();
        detailPersonilBloc.add(Detail());
        Navigator.push(
            context, MaterialPageRoute(builder: (context) => const MainPage()));
      } else {
        showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
              title: Text('Error'),
              content: Text('Login Gagal'),
              actions: [
                ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: Text('Tutup'),
                ),
              ],
            );
          },
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    var detailPersonilBloc = BlocProvider.of<DetailpersonilBloc>(context);
    width = MediaQuery.of(context).size.width;
    height = MediaQuery.of(context).size.height;
    return Scaffold(
      body: Stack(
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: const [
              Opacity(
                opacity: 0.5,
                child: Image(
                  image: AssetImage('images/bg-map-min.png'),
                ),
              ),
            ],
          ),
          SingleChildScrollView(
            padding: EdgeInsets.only(top: 100),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                  height: 100,
                  width: width,
                  child: const Image(
                    image: AssetImage('images/login-logo.png'),
                    alignment: Alignment.center,
                  ),
                ),
                Container(
                  padding: const EdgeInsets.all(15),
                  child: const Text(
                    'GAJI DAN TUNKIN',
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                ),
                Form(
                  key: formKey,
                  child: Column(
                    children: [
                      Container(
                        margin: const EdgeInsets.all(20 - 2.6),
                        child: Card(
                          elevation: 10,
                          child: Container(
                            padding: const EdgeInsets.all(20),
                            child: Column(
                              children: [
                                Container(
                                  alignment: Alignment.topLeft,
                                  padding: const EdgeInsets.only(bottom: 20),
                                  child: const Text(
                                    'LOGIN',
                                    style: TextStyle(
                                      fontSize: 20,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                ),
                                Container(
                                  padding: const EdgeInsets.only(bottom: 25),
                                  child: TextFormField(
                                    validator: (value) {
                                      if (value == null || value.isEmpty) {
                                        return 'Masukkan NRP/NIP';
                                      }
                                      return null;
                                    },
                                    controller: nrpController,
                                    decoration: InputDecoration(
                                      labelText: 'NRP/NIP',
                                      hintText: 'Masukkan NRP/NIP',
                                      prefixIcon: Icon(Icons.person,
                                          color: Colors.blue.shade700),
                                      border: OutlineInputBorder(
                                        borderRadius: BorderRadius.circular(10),
                                      ),
                                    ),
                                  ),
                                ),
                                TextFormField(
                                  obscureText: !passwordVisible,
                                  validator: (value) {
                                    if (value == null || value.isEmpty) {
                                      return 'Masukkan Kata Sandi';
                                    }
                                    return null;
                                  },
                                  controller: sandiController,
                                  decoration: InputDecoration(
                                    labelText: 'Kata Sandi',
                                    hintText: 'Masukkan Kata Sandi',
                                    prefixIcon: Icon(Icons.lock,
                                        color: Colors.blue.shade700),
                                    suffixIcon: IconButton(
                                      onPressed: () {
                                        setState(() {
                                          passwordVisible = !passwordVisible;
                                        });
                                      },
                                      icon: Icon(
                                        passwordVisible
                                            ? Icons.visibility
                                            : Icons.visibility_off,
                                        color: Colors.blue.shade700,
                                      ),
                                    ),
                                    border: OutlineInputBorder(
                                      borderRadius: BorderRadius.circular(10),
                                    ),
                                  ),
                                )
                              ],
                            ),
                          ),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () async {
                          await onLogin(detailPersonilBloc);
                        },
                        child: const Text('LOGIN'),
                        style: ElevatedButton.styleFrom(
                          primary: Colors.blue.shade700,
                          minimumSize: const Size(200, 40),
                        ),
                      )
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

加记页面

class GajiPage extends StatefulWidget {
  const GajiPage({Key key}) : super(key: key);

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

class _GajiPageState extends State<GajiPage> {
  double width = 0;
  double height = 0;

  var currentYear = DateTime.now().year;
  var currentMonth = DateTime.now().month;
  DetailpersonilBloc detailPersonilBloc;
  GajiBloc gajiBloc;

  @override
  void initState() {
    setState(() {
      detailPersonilBloc = DetailpersonilBloc();
      detailPersonilBloc.add(Detail());
      setMonth();
      setYear();
      gajiBloc = GajiBloc();
      gajiBloc.add(Gaji());
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var detailPersonilBloc = BlocProvider.of<DetailpersonilBloc>(context);
    width = MediaQuery.of(context).size.width;
    height = MediaQuery.of(context).size.height;
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Image(
          image: const AssetImage('images/header-logo.png'),
          width: width / 2,
        ),
        flexibleSpace: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: [
                Color.fromARGB(255, 170, 177, 175),
                Color.fromARGB(255, 197, 217, 212)
              ],
            ),
          ),
        ),
      ),
      body: Stack(
        children: [
          BlocBuilder<GajiBloc, GajiState>(
            builder: (context, state) {
              return state is GajiLoaded
                  ? ListView(
                      children: [
                        Container(
                          height: 100,
                        ),
                        Card(
                          color: const Color.fromARGB(255, 74, 50, 152),
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(20)),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Container(
                                margin: const EdgeInsets.all(10),
                                child: const Text(
                                  'Gaji Bersih',
                                  style: TextStyle(
                                    fontSize: 20,
                                    color: Colors.white,
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(10),
                                child: Text(
                                  NumberFormat.currency(
                                          locale: 'en',
                                          symbol: 'RP ',
                                          decimalDigits: 0)
                                      .format(state.bersih),
                                  style: TextStyle(
                                    fontWeight: FontWeight.w700,
                                    fontSize: 40,
                                    color: Colors.white,
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                        Card(
                          child: Column(
                            children: [
                              Container(
                                color: const Color.fromARGB(255, 238, 238, 238),
                                width: width,
                                child: Row(
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                                  children: [
                                    Container(
                                      margin: const EdgeInsets.fromLTRB(
                                          10, 10, 0, 10),
                                      width: (width / 2) - 25,
                                      child: const Text(
                                        'Detail Gaji',
                                        textAlign: TextAlign.start,
                                        style: TextStyle(
                                          fontWeight: FontWeight.w600,
                                          fontSize: 20,
                                        ),
                                      ),
                                    ),
                                    Container(
                                      margin: const EdgeInsets.fromLTRB(
                                          5, 10, 20, 10),
                                      width: (width / 2) - 18,
                                      child: Text(
                                        '${state.bulan} - ${state.tahun}',
                                        textAlign: TextAlign.end,
                                        style: TextStyle(
                                          fontWeight: FontWeight.w600,
                                          fontSize: 20,
                                        ),
                                      ),
                                    )
                                  ],
                                ),
                              ),
                              ...
                            ],
                          ),
                        ),
                        Container(
                          height: 50,
                        ),
                      ],
                    )
                  : Center(
                      child: Text(
                          'Tidak ada data. Data gaji bulan ${state.bulan} belum diproses'),
                    );
            },
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Center(
                child: BlocBuilder<DetailpersonilBloc, DetailpersonilState>(
                  builder: (context, state) {
                    return Card(
                      child: Row(
                        children: [
                          Container(
                            margin: const EdgeInsets.all(10),
                            child: const CircleAvatar(
                              backgroundImage: AssetImage('images/Profpic.PNG'),
                              radius: 30,
                            ),
                          ),
                          Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Container(
                                  width: 250,
                                  padding: const EdgeInsets.all(5),
                                  child: Text(
                                    state.nama,
                                    style: const TextStyle(
                                        fontSize: 20,
                                        fontWeight: FontWeight.bold),
                                  )),
                              Container(
                                  padding: const EdgeInsets.all(5),
                                  child: Text(
                                    state.nrp,
                                    style: const TextStyle(fontSize: 15),
                                  )),
                            ],
                          ),
                          GestureDetector(
                            onTap: () {
                              detailPersonilBloc.add(Detail());
                              showModalBottomSheet(
                                backgroundColor: Colors.transparent,
                                isScrollControlled: true,
                                context: context,
                                builder: (context) => detailsBottomSheet(),
                              );
                            },
                            child: const Text(
                              'DETAILS',
                              style: TextStyle(color: Colors.blue),
                            ),
                          )
                        ],
                      ),
                    );
                  },
                ),
              ),
              GestureDetector(
                onTap: () {
                  showModalBottomSheet(
                    backgroundColor: Colors.transparent,
                    isScrollControlled: true,
                    context: context,
                    builder: (context) {
                      return StatefulBuilder(
                        builder: (context, StateSetter setState) {
                          return filterBottomSheet();
                        },
                      );
                    },
                  );
                },
                child: Container(
                  height: 50,
                  width: width,
                  decoration: const BoxDecoration(
                    color: Color.fromARGB(255, 244, 244, 244),
                  ),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Container(
                        margin: const EdgeInsets.only(right: 3),
                        child: const Icon(
                          Icons.tune,
                          color: Color.fromARGB(255, 45, 165, 217),
                        ),
                      ),
                      const Text(
                        'Filter',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontWeight: FontWeight.w500,
                          fontSize: 20,
                          color: Color.fromARGB(255, 45, 165, 217),
                        ),
                      ),
                    ],
                  ),
                ),
              )
            ],
          )
        ],
      ),
    );
  }
}

注2:“...”是一堆不需要复制的代码。

答案出奇地简单,正如我从实习中学到的那样。 Bloc 没有更新的原因是因为我没有使用应用程序的上下文。因此,当我在我的页面中生成一个新的 Bloc 时,它是“空的”。所以解决方案是使用 context.read().add(BlocEvent()) 或使用 BlocProvider.of(context) 创建一个新的 bloc,然后添加事件。基本上必须为 bloc 提供应用程序的原始上下文。