escamoteur / watch_it

MIT License
120 stars 9 forks source link

How to Watch Optional Property #8

Closed cliftonlabrum closed 1 year ago

cliftonlabrum commented 1 year ago

I have an optional selectedFlight property in a class like this:

class ModelFlight with ChangeNotifier {
  //+++
  static ModelFlight get to => di<ModelFlight>();
  //+++

  Flight? _selectedFlight;
  Flight? get selectedFlight => _selectedFlight;
  set selectedFlight(Flight? value) {
    _selectedFlight = value;

    notifyListeners();
  }
}

...but it crashes when I set up the property value watcher in a widget:

Widget build(BuildContext context) {
  watchPropertyValue((ModelFlight model) => model.selectedFlight); //<-- !! Exception !!
}

__TypeError (Null check operator used on a null value)_

How can I watch a nullable property that won't be set until later?

escamoteur commented 1 year ago

can you paste the full stacktrace of when this happens? Might be a bug in watch_it

cliftonlabrum commented 1 year ago

Here is the stack trace:

════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building MyApp(dirty):
Null check operator used on a null value

The relevant error-causing widget was
MyApp
main.dart:6
When the exception was thrown, this was the stack
#0      watchPropertyValue
watch_it.dart:156
#1      MyApp.build
main.dart:15
#2      StatelessElement.build
framework.dart:5367
#3      _GetItElement.build
elements.dart:20
#4      ComponentElement.performRebuild
framework.dart:5297
#5      Element.rebuild
framework.dart:5016
#6      ComponentElement._firstBuild
framework.dart:5279
#7      ComponentElement.mount
framework.dart:5273
#8      _GetItElement.mount
elements.dart:10
...     Normal element mounting (27 frames)
#35     Element.inflateWidget
framework.dart:4182
#36     Element.updateChild
framework.dart:3707
#37     RenderObjectToWidgetElement._rebuild
binding.dart:1253
#38     RenderObjectToWidgetElement.mount
binding.dart:1222
#39     RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure>
binding.dart:1169
#40     BuildOwner.buildScope
framework.dart:2719
#41     RenderObjectToWidgetAdapter.attachToRenderTree
binding.dart:1168
#42     WidgetsBinding.attachRootWidget
binding.dart:1001
#43     WidgetsBinding.scheduleAttachRootWidget.<anonymous closure>
binding.dart:981
#47     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
(elided 3 frames from class _Timer and dart:async-patch)
════════════════════════════════════════════════════════════════════════════════

It looks like the crash is on line 156 of watch_it.dart where you are force-unwrapping observedProperty (which, in my case, is null at the moment).

assert(observedProperty! is! Listenable, 'selectProperty returns a Listenable. Use watchIt instead');

Here is a simple project reproducing it:

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

void main() {
  di.registerSingleton<Test>(Test());
  runApp(const MyApp());
}

class MyApp extends StatelessWidget with WatchItMixin {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    //~~~
    watchPropertyValue((Test x) => x.selectedThing);
    //~~~
    return MaterialApp(
      title: 'Watch It Test',
      home: Column(
        children: [
          Text('Testing: ${Test.to.selectedThing}'),
          TextButton(
            onPressed: () {
              final thing = Thing(name: 'hi');
              Test.to.selectedThing = thing;
            },
            child: const Text('Set'),
          ),
        ],
      ),
    );
  }
}

class Test with ChangeNotifier {
  static Test get to => di<Test>();

  Thing? _selectedThing;
  Thing? get selectedThing => _selectedThing;
  set selectedThing(Thing? value) {
    _selectedThing = value;
    notifyListeners();
  }
}

class Thing {
  String name;

  Thing({
    required this.name,
  });
}

(As a side note, I use Test.to.selectedThing in my widget instead of final thing = watchPropertyValue((Test x) => x.selectedThing); because it makes reactive variables easier to find in my widget code. 😊)

escamoteur commented 1 year ago

Fixed in V1.0.0

cliftonlabrum commented 1 year ago

The fix works great! Thank you! 😄

escamoteur commented 1 year ago

You found an important bug that might have impacted others, so thanks for the report and analysis Am 24. Aug. 2023, 19:47 +0200 schrieb Clifton Labrum @.***>:

The fix works great! Thank you! 😄 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you modified the open/close state.Message ID: @.***>