escamoteur / watch_it

MIT License
103 stars 8 forks source link

Watching a future returning function leads to type case error #29

Closed Hollow-Ego closed 1 month ago

Hollow-Ego commented 1 month ago

Hi there,

as the title describes an error is thrown if you try to watch a future based on a function that is returning a future.

Error:

type '() => ItemTemplateViewModel?' is not a subtype of type 'ItemTemplateViewModel?' in type cast

Stacktrace
I/flutter ( 7540): #0   _WatchItState.registerFutureHandler (package:watch_it/src/watch_it_state.dart:398:42)
I/flutter ( 7540): #1   watchFuture (package:watch_it/src/watch_it.dart:268:31)
...

Line throwing the error:

initialValueProvider?.call as R?;

watchFuture call:

final itemTemplate = watchFuture((LibraryService p0) => p0.getItemTemplateById(templateId ?? -1), initialValue: null);

getItemTemplateById signature:

Future<ItemTemplateViewModel?> getItemTemplateById(int templateId) 

I suspect this might be a bug in registerFutureHandler. The following is the code block including the line throwing the error and the code before it

/// select returned a different value than the last time
/// so we have to unregister out handler and subscribe anew
watch.dispose();
initialValue = preserveState && watch.lastValue!.hasData
    ? watch.lastValue!.data ?? initialValueProvider?.call()
    : initialValueProvider?.call as R?;

initialValueProvider?.call as R?; is trying to cast the function to R instead of calling it and then casting. Given that at all other times (in this file) the .call is actually being called, this is most likely a bug. Changing it to : initialValueProvider?.call() as R?; works without throwing an error.

escamoteur commented 1 month ago

Thanks for reporting, I will look into it

escamoteur commented 1 month ago

Hi,

I added a new branch and PR with a fix https://github.com/escamoteur/watch_it/pull/30

Could you please verify that it's works (you can directly reference the branch from your pubspec) It seems that your selector function (LibraryService p0) => p0.getItemTemplateById(templateId ?? -1) returns different futures on different builds which is probably pretty rarely used. watch_it was built to deal with this, but you just found the bug there.

Hollow-Ego commented 1 month ago

Hi,

thanks for the quick fix. I tested it out and it works.

One thing I noticed is that this only happened when templateId was null. getItemTemplateById ultimately calls the following implementation interacting with the local Drift database

Future<ItemTemplateViewModel?> getItemTemplateWithId(int id) async {
    final row = await _joinValues(select(itemTemplates)..where((li) => li.id.equals(id))).getSingleOrNull();
    return _rowToViewModel(row);
  }
escamoteur commented 1 month ago

It might be, that it only really happend when the returned value was null. But in general I wouldn't implement it this way as it really will create a new Future on every call that is awaited and then leads to a rebuild which creates a new future. I prefer to have such functions updating a ´ValueNotifier´ when a new value is available and then watch that one. I highly recommend checking out my flutter_command package as it is a great addition to watch_it especially to handle async function calls

escamoteur commented 1 month ago

Fixed in V1.4.2 now on pub