Coder Social home page Coder Social logo

get_it_mixin's Introduction

get_it_mixin

A set of mixins that allow widgets to watch data registered with GetIt. Widgets that watch data will rebuild automatically whenever that data changes.

Supported data types that can be watched are ChangeNotifier, ValueNotifier, Stream and Future.

ChangeNotifier based example:

// Create a ChangeNotifier based model
class UserModel extends ChangeNotifier {
  get name = _name;
  String _name = '';
  set name(String value){
    _name = value;
    notifyListeners();
  }
  ...
}

// Register it 
getIt.registerSingleton<UserModel>(UserModel());

// Watch it
class UserNameText extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    final userName = watchOnly((UserModel m) => m.name);
    return Text(userName);
  }
}

Reading Data

Reading data is already quite easy with GetIt, but it gets even easier with the mixin. Just add a GetItMixin to a StatelessWidget and call get<T>:

class MyWidget extends StatelessWidget with GetItMixin {
  void _handleSubmitPressed() {
    final email = get<Model>().emailAddress;
    ...
  }
}

You can do the same thing on StatefulWidget using a GetItStatefulWidgetMixin and GetItStateMixin:

class MyWidget extends StatefulWidget with GetItStatefulWidgetMixin {
  _MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with GetItStateMixin {
  void _handleSubmitPressed() {
    final email = get<Model>().emailAddress;
    ...
  }
}

NOTE: The GetItMixin API is generally the same regardless of whether you use Stateless or Stateful widgets.

Watching Data

Where GetItMixin really shines is data-binding. It comes with a set of watch methods to rebuild a widget when data changes.

Imagine you had a very simple shared model, with multiple fields, one of them being country:

class Model {
    final country = ValueNotifier<String>('Canada');
    ...
}
getIt.registerSingleton<Model>(Model());

You could tell your view to rebuild any time country changes with a simple call to watchX:

class MyWidget extends StatelessWidget with GetItStatefulWidgetMixin {
  @override
  Widget build(BuildContext context) {
    String country = watchX((Model x) => x.country);
    ...
  }
}

There are various watch methods, for common types of data sources, including ChangeNotifier, ValueNotifier, Stream and Future:

API Description
.watch Bind to a ValueListenable value
.watchX Bind to the results of a select method on a ValueListenable value
.watchOnly Bind to a basic Listenable (like ChangeNotifier)
.watchXOnly Bind to the results of a select method on a Listenable
.watchStream Subscribe to a Stream
.watchFuture Bind to a Future

Just call watch_ to listen to the data type you need, and GetItMixin will take care of cancelling bindings and subscriptions when the widget is destroyed.

The primary benefit to the watch methods is that they eliminate the need for ValueListenableBuilders, StreamBuilder etc. Each binding consumes only one line and there is no nesting.

Here we watch three ValueListenable which would normally be three builders, 12+ lines of code and several levels of indentation. With GetItMixin, it's three lines:

class MyWidget extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    bool loggedIn = watchXOnly((UserModel x) => x.isLoggedIn);
    String userName = watchXOnly((UserModel x) => x.user.name);
    bool darkMode = watchXOnly((SettingsModel x) => x.darkMode);
    ...
  }
}

This can be used to eliminate StreamBuilder and FutureBuilder from your UI as well:

class MyWidget extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    final currentUser = watchStream((UserModel x) => x.userNameUpdates, 'NoUser');
    final ready = watchFuture((AppModel x) => x.initializationReady, false).data;
    bool appIsLoading = ready == false || currentUser.hasData == false;
    
    if(appIsLoading) return CircularProgressIndicator();
    return Text(currentUser.data);    
  }
}

Side Effects / Event Handlers

Instead of rebuilding, you might instead want to show a toast notification or dialog when a Stream emits a value or a ValueListenable changes.

To run an action when data changes you can use the register methods:

API Description
.registerHandler Add an event handler for a ValueListenable
.registerStreamHandler Add an event handler for a Stream
.registerFutureHandler Add an event handler for a Future

The first param in the register methods is a select delegate that can be used to watch a specific field. The second param is the action which will be triggered when that field changes:

class MyWidget extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    registerHandler(
        (Model x) => x.name,
        (context, value, cancel) => showNameDialog(context, value));
    ...
  }
}

In the example above you see that the handler function receives the value that is returned from the select delegate ((Model x) => x.name), as well as a cancel function that the handler can call to cancel registration at any time.

As with watch calls, all registerHandler calls are cleaned up when the Widget is destroyed.

Rules

There are some important rules to follow in order to avoid bugs with the watch methods:

  • watch methods must be called within build()
    • It is good practice to define them at the top of your build method
  • must be called on every build, in the same order (no conditional watching). This is similar to flutter_hooks.
  • do not use them inside of a builder as it will break the mixins ability to rebuild

isReady() and allReady()

A common use case is to toggle a loading state when side effects are in-progress. To check whether any registered actions have completed you can use allReady() and isReady<T>(). These methods return the current state of any registered async operations and a rebuild is triggered when they change.

class MyWidget extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    allReady(onReady: (context)
      => Navigator.of(context).pushReplacement(MainPageRoute()));
    return CircularProgressIndicator();
  }
}

Check out the GetIt docs for more information on the isReady and allReady functionality: https://pub.dev/packages/get_it

Pushing a new GetIt Scope

With pushScope() you can push a scope when a Widget/State is mounted, and automatically pop when the Widget/State is destroyed. You can pass an optional init or dispose function.

  void pushScope({void Function(GetIt getIt) init, void Function() dispose});

This can be very useful for injecting mock services when views are opened so you can easily test them. Of course you can also push and pop scope directly from your tests as well!

Find out more!

For more background on the history of GetItMixin you can check out the README_EXTENDED.

For a more complete explanation of the API watch the presentation: GetIt in action By Thomas Burkhart.

get_it_mixin's People

Contributors

escamoteur avatar esdotdev avatar franzlst avatar m-sadegh-sh avatar rayliverified avatar ruzo avatar tayormi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

get_it_mixin's Issues

Strange error with get_it_mixin

I installed latest versions of AndroidStudio, Flutter, plugins etc. Since I had different versions of AndroidStudio I cleaned up everything and re-installed from scratch. I started replacing Stateful with Stateless Widgets and added get_it_mixin. When building the project I get the following error:

Launching lib/main.dart on iPhone 13 in debug mode...
Running Xcode build...
Xcode build done.                                           10.5s
Debug service listening on ws://127.0.0.1:53118/H_LQeT1mgxM=/ws
Syncing files to device iPhone 13...
../../../../Applications/flutter/.pub-cache/hosted/pub.dartlang.org/get_it_mixin-3.1.3/lib/src/mixin_state.dart:495:7: Warning: Operand of null-aware operation '!' has type 'void Function(BuildContext, AsyncSnapshot<R?>, void Function())' which excludes null.
 - 'BuildContext' is from 'package:flutter/src/widgets/framework.dart' ('../../../../Applications/flutter/packages/flutter/lib/src/widgets/framework.dart').
 - 'AsyncSnapshot' is from 'package:flutter/src/widgets/async.dart' ('../../../../Applications/flutter/packages/flutter/lib/src/widgets/async.dart').
      handler!(_element!, watch.lastValue!, watch.dispose);
      ^

The app starts but does not work since I implemented get_it_mixin on my home screen.

It happens also with Android Simulator, e.g. Pixel 4. Flutter doctor does not report any issues:

[Google] flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.5.3, on macOS 11.6.1 20G224 darwin-x64, locale en-CH)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] VS Code (version 1.62.3)
[✓] Connected device (2 available)

• No issues found!

I tried flutter clean but that didn't help.

Any idea what is wrong here?

Put full license text in LICENSE file

In contrast to other packages, get_it_mixin does not put the full license text in LICENSE, but only "MIT". This makes it hard to automate license extraction, e.g. for SBOM creation. Currently, I'm evaluation LicenseFinder, which stumbles upon this package and cannot extract its license.

getItMixin web support

Does getItMixin works on web @escamoteur ? Because in the Package it says yes.. so i brought this issue.

I have a project and i use getIt+getItMixin within it.
It works all good on mobile, but when trying to execute on web it gives me these errors:

I'm using:

get_it: ^6.0.0-nullsafety.2
get_it_mixin: ^2.0.1

image

The following TypeErrorImpl was thrown building HomePage(dirty):
Expected a value of type 'LinkedList<_WatchEntry<Object, Object>>?', but got one of type 'LinkedList<_WatchEntry<Object, Object?>>'

The relevant error-causing widget was
HomePage
lib\main.dart:28
When the exception was thrown, this was the stack
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49      throw_
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 84:3        castError
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 442:10  cast
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart 417:9        as
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/collection/linked_list.dart 224:18                                   set [_list]


I saw that when using the GetItMixin or GetItStatefullWidgetMixin i have the same error even for the simpliest example.
See below a code sample that generate the errors.

void main() {
  setup();
  runApp(MyApp());
}

final getIt = GetIt.instance;

void setup() async {
  getIt.registerSingleton<PlayerViewModel>(PlayerViewModel());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIOverlays([]);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      home: HomePage(),
    );
  }
}


class HomePage extends StatelessWidget with GetItMixin {
  @override
  build(BuildContext context) {
    int count = 0;

    final name = watchOnly((PlayerViewModel v) => v.name);
    // final name = getIt<PlayerViewModel>().name;

    return Scaffold(
      body: Container(
        color: Colors.deepPurple,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              name,
              style: TextStyle(
                color: Colors.red,
              ),
            ),
            ElevatedButton(
                onPressed: () {
                  count++;
                  getIt<PlayerViewModel>().name = 'X $count';
                },
                child: Text('ok'))
          ],
        ),
      ),
    );
  }
}


import 'package:flutter/foundation.dart';

class PlayerViewModel extends ChangeNotifier {
  String _name = 'NAME TEST';
  String get name => _name;

  set name(String name) {
    _name = name;
    notifyListeners();
  }
}


If i change:
final name = watchOnly((PlayerViewModel v) => v.name);

for the code below it works good, and the page is rendered normally.
final name = getIt().name;

Edit.: adds the version of both packages.:

How to use registerHandler for same subsequent calls ?

I am trying to use registerHandler to display the gallery permission dialog whenever the user clicks to open the gallery. However, I am having trouble integrating and using it properly.

The problem is that it only works the first time, and subsequent calls to isGalleryPermissionDenied.value = true; do not trigger notifyListeners as expected. I have tried manually calling isGalleryPermissionDenied.notifyListeners(), but this causes it to trigger twice initially.

How can I properly use registerHandler to display dialog if gallery is denied and trigger notifyListeners as expected?

class ProfilePhotoScreen extends StatelessWidget with GetItMixin {
  ProfilePhotoScreen({Key? key}) : super(key: key);

  void onGalleryPermissionDenied(context, newValue, cancel) {
    showPermissionDeniedDialogNew(...);
  }
  
  @override
  Widget build(BuildContext context) {
    registerHandler((ProfilePhotoViewModel vm) => vm.isGalleryPermissionDenied, onGalleryPermissionDenied);
    ...
  }
}

class ProfilePhotoViewModel with Disposable {
  ProfilePhotoViewModel({required this.imageService});
  
  ValueNotifier<bool> isGalleryPermissionDenied = ValueNotifier(false);
  
   void pickImage(BuildContext context) {
    ...
    isGalleryPermissionDenied.value = true;
  }
}

Not able to recognize the return types. " The return type 'void' isn't a 'ValueListenable<R>', as required by the closure's context."

Hi escamoteur,

I do not know if this is the right package or should I post this issue on flutter_command repo. But I think the issue is with maybe here.

Let us take a simple counter example. I register a singleton and then when I try to use the watchX command, I get an error stating void is not a valid return type.
however, the return type is a string . eg. Command<void, String> so it is not void. So why cannot watchX get the return type correct. Am I doing something wrong.?

example code .

import 'package:get_it/get_it.dart';
import 'package:get_it_mixin/get_it_mixin.dart';
import 'package:flutter_command/flutter_command.dart';
import 'package:flutter/material.dart';

final GetIt locator = GetIt.instance;

void main() {
  setup();
  runApp(MyApp());
}

void setup() {
  locator.registerSingleton<CounterManager>(
    CounterManager(),
  );
}

class CounterManager {
  int counter = 0;
  Command<void, String> incrementCounterCommand;
  CounterManager() {
    incrementCounterCommand = Command.createSyncNoParam(() {
      counter++;
      return counter.toString();
    }, '0');

  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'GetIt Mixins',
      theme: ThemeData.light().copyWith(primaryColor: Color(0xFFe13133)),
      home: GetItMixinsScreen(),
    );
  }
}

class GetItMixinsScreen extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {
    final counter  =
        watchX((CounterManager x) => x.incrementCounterCommand.execute());
   
    return Scaffold(
      appBar: AppBar(
        title: Text("Counter"),
      ),
      body: Container(
        child: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              padding: EdgeInsets.all(8.0),
              decoration: kWhiteBackground,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    "Counter",
                    style: Theme.of(context).textTheme.headline4,
                  ),

                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

flutter pubspec.yaml

flutter_command: ^2.0.1
  functional_listener: ^2.0.2
  get_it_mixin: ^3.1.3
```



flutter doctor results 

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.8.1, on macOS 12.1 21C52 darwin-x64, locale en-DK)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] VS Code (version 1.63.2)
[✓] Connected device (2 available)

Watch list value changes don't trigger rebuild

Hi guys, thanks for this amazing package. Let me try to explain my doubt:

I have the follow class:

class EditProfileManager extends ChangeNotifier {
  final SupabaseAuthService _api;

  EditProfileManager(this._api);

  final List<String> _photosPaths = List.filled(5, '');

  get photos => _photosPaths;

  void getCurrentPhotos() {
    appManager.currentProfile.photos.asMap().forEach((index, value) {
      _photosPaths[index] = value;
    });
    notifyListeners();
  }

  void updatePhoto(index, String path) {
    _photosPaths[index] = path;
    notifyListeners();
  }
}

I update some values of my _photosPaths, and tried to rebuild my widget with this new value, but nothing happens ( only if i manually refresh ).
I watching the changes like that:

class GridPhotosViewer extends StatelessWidget with GetItMixin {
  GridPhotosViewer({super.key});

  @override
  Widget build(BuildContext context) {
    final photosPaths = watchX((EditProfileManager m) => m.photos);

    return Container(
      padding: const EdgeInsets.all(8),
      child: StaggeredGrid.count(
        axisDirection: AxisDirection.down,
        crossAxisCount: 4,
        mainAxisSpacing: 4,
        crossAxisSpacing: 4,
        children: List.generate(photosPaths.length, (index) {

I'm doing something wrong ?

registerStreamHandler causes unnecessary re-renders

When using handlers, they cause the the component with the mixin to re-render on each "change".

Here's a simple example reproducing the issue

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:get_it_mixin/get_it_mixin.dart';

void main() {
  setup();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget with GetItStatefulWidgetMixin {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> with GetItStateMixin {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    print('build');
    registerStreamHandler<AppModel, String>((x) => x.stream,
        (context, x, cancel) {
      print('handle stream');
    });
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => get<AppModel>().streamController.add("event"),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

final getIt = GetIt.instance;

void setup() {
  getIt.registerLazySingleton(() => AppModel());
}

class AppModel extends ChangeNotifier {
  // ignore: close_sinks
  final StreamController<String> streamController =
      StreamController<String>.broadcast();

  Stream<String> get stream => streamController.stream;
}

Notice that each button tap causes the build statement to be printed.

Question Regarding Example in Readme

I have a simple question. The Readme shows this example:

class UserModel extends ChangeNotifier {
  get name = _name;
  String _name = '';
  set name(String value){
    _name = value;
    notifyListeners();
  }
  ...
}

Why create a getter that points to _name? Isn't that redundant to just using name?

class UserModel extends ChangeNotifier {
  String name = '';
  set name(String value){
    name = value;
    notifyListeners();
  }
  ...
}

Can you help me understand the need for a getter alongside a property? Thanks! 😊

error in readme

Obviously there is an error in readme in the following piece of code

String _emailAddress;
set country(String val) {
  _emailAddress = val;
  notifyListeners();
}

there should be

set emailAddress(String val) { ...

ReadMe Proofreading

Starting a proofreading thread to consolidate errors and suggestions for improvements.

WatchStream Exception When Handling AsyncSnapshot<Type> Instead of Type

watchStream might be broken because it tries to return a AsyncSnapshot instead of the

I am trying to watch the Firebase Auth.
final User user = watchStream((AuthService auth) => auth.user, null).data;

The Stream in AuthService is
Stream<User> get user => _auth.authStateChanges();

The following error is thrown:
type 'AsyncSnapshot<User>' is not a subtype of type 'User'

Error location:

Dart Unhandled Exception: type 'AsyncSnapshot' is not a subtype of type 'User' of 'value', stack trace: #0 _WatchEntry.lastValue= (package:get_it_mixin/src/mix_in.dart)
_MixinState.watchStream. (package:get_it_mixin/src/mix_in.dart:793:15)

Using the watch calls on specific GetIt instance?

This is not a bug report, rather a request or a question on how to achieve a certain setup.

I would like to use get_it_mixin in a way that makes it possible that each instance of a widget has its own "view model" (state), backed by get_it_mixin.

So when my widget gets created, my view model object should be created in a one-to-one mapping. Here is where I run into problems registering them as singletons with the global GetIt, since it would give me the same view model instance each time.

I have experimented with pushScope but I'm not sure if that is the correct path.

The other idea is that my widget creates it own GetIt-instance but then the watch calls all work with the global GetIt instance, so that breaks.

Any ideas on how to do this? Also, am I doing something that seems completely wrong?
Any advice appreciated. Cheers!

[EDIT]
The same problem formulation and a solution with Riverpod

Operand of null-aware operation '!' has type ... which excludes null

Hi @escamoteur, since upgrading to Flutter 2.2.2 and get_it_mixin 3.1.3, I'm noticing the following error when debugging my app:

../../../../.pub-cache/hosted/pub.dartlang.org/get_it_mixin-3.1.3/lib/src/mixin_state.dart:495:7: Warning: Operand of null-aware operation '!' has type 'void Function(BuildContext, AsyncSnapshot<R?>, void Function())' which excludes null.
 - 'BuildContext' is from 'package:flutter/src/widgets/framework.dart' ('../../../../fvm/versions/stable/packages/flutter/lib/src/widgets/framework.dart').
 - 'AsyncSnapshot' is from 'package:flutter/src/widgets/async.dart' ('../../../../fvm/versions/stable/packages/flutter/lib/src/widgets/async.dart').
      handler!(_element!, watch.lastValue!, watch.dispose);
      ^

Here's the referenced code in mixin_state:

    watch.lastValue = AsyncSnapshot<R?>.withData(
        ConnectionState.waiting, _initialValue ?? initialValueProvider?.call());
    if (executeImmediately) {
      handler!(_element!, watch.lastValue!, watch.dispose);
    }

    return watch.lastValue!;
  }

Thanks!

Error using pushScope

I have a new page that initializes its dependency in the build method like so:

  Widget build(BuildContext context) {
    pushScope(
        init: (locator) => locator.registerSingleton(PreferencesViewModel()));

    return Scaffold(
      body: SafeArea(child: _buildContent(context)),
    );
  }

I get the following exception after popping the page:

Another exception was thrown: You already have used the scope name null

I/flutter (14985): 'package:get_it/get_it_impl.dart':
I/flutter (14985): Failed assertion: line 732 pos 7: '_scopes.firstWhereOrNull((x) => x.name == scopeName) == null'
I/flutter (14985): 
I/flutter (14985): The relevant error-causing widget was:
I/flutter (14985):   PreferencesPage
I/flutter (14985):   file:///hello_world/app/preferences_page.dart:14:56
I/flutter (14985): 
I/flutter (14985): When the exception was thrown, this was the stack:
I/flutter (14985): #2      _GetItImplementation.pushNewScope (package:get_it/get_it_impl.dart:732:7)
I/flutter (14985): #3      _MixinState.pushScope (package:get_it_mixin/src/mixin_state.dart:540:15)
I/flutter (14985): #4      GetItMixin.pushScope (package:get_it_mixin/src/mixin.dart:240:20)
I/flutter (14985): #5      PreferencesPage.build (package:serqit_app/ui/preferences/preferences_page.dart:18:5)
I/flutter (14985): #6      StatelessElement.build (package:flutter/src/widgets/framework.dart:4648:28)
I/flutter (14985): #7      _GetItElement.build (package:get_it_mixin/src/elements.dart:16:18)
I/flutter (14985): #8      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4574:15)
I/flutter (14985): #9      Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
I/flutter (14985): #10     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4553:5)
I/flutter (14985): #11     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4548:5)
I/flutter (14985): #12     _GetItElement.mount (package:get_it_mixin/src/elements.dart:9:11)

I am not sure what I am doing wrong if anything.

registerHandler before a watchX causes a crash

I opened this one in flutter_command, but it turns out to be an issue with get_it_mixin. It is caused by registerHandler using watchX internally. This causes a runtime error.

I found a situation that causes an error and it isn't clear why it's an error.

If I use a
registerHandler((StringManager sm) => sm.transformString, (context, name, _) => print(name));
before
var message2 = watchX((StringManager sm) => sm.transformString);

this causes an error. However if I reverse the order of the statements, there is no error & everything works just fine.

What's going on here?

Thank you!

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_command/flutter_command.dart';
import 'package:get_it/get_it.dart';
import 'package:get_it_mixin/get_it_mixin.dart';

void main() {
  var sm = StringManager();
  GetIt.I.registerSingleton<StringManager>(sm);
  sm.loadStringCommand.execute();

  runApp(MaterialApp(home: MyApp()));
}

class StringManager {
  Command<void, String> loadStringCommand;
  ValueListenable<String> transformString;

  StringManager() {
    loadStringCommand =
        Command.createAsync<void, String>(_stringService, 'initial');

    transformString =
        loadStringCommand.map<String>((result) => result.toUpperCase());
  }

//TODO why is 'void x' needed as a parameter?
  Future<String> _stringService(void x) async {
    await Future.delayed(Duration(seconds: 2));
    return 'thank you for waiting';
  }
}

class MyApp extends StatelessWidget with GetItMixin {
  @override
  Widget build(BuildContext context) {


    var message = watchX((StringManager sm) => sm.loadStringCommand);
    var message2 = watchX((StringManager sm) => sm.transformString);

    registerHandler((StringManager sm) => sm.transformString,
            (context, name, _) => print(name));

    return Scaffold(
      appBar: AppBar(
        title: Text('hello'),
      ),
      body: Column(
        children: [Text(message), Text(message2)],
      ),
    );
  }
}

`Watch` does not trigger rebuild

I have a custom object Strokes which implements changeNotifier.
I watch it with:

final Strokes strokes = watchOnly((Strokes s) => s);

This object has two methods deleteLastStroke():

    // get all strokes except for the last one
    var p = _path.computeMetrics().take(_path.computeMetrics().length - 1);
    var newPath = Path();
    // copy the strokes to a new Path
    p.forEach((element) {
      newPath.addPath(element.extractPath(0, double.infinity), Offset.zero);
    });
    _path = newPath;

    decrementStrokeCount();

    notifyListeners();

and deleteAllStrokes():

    _path.reset();
    _strokeCount = 0;

    notifyListeners();

Both of them call notifyListeners() however only deleteAllStrokes() triggeres a rebuild of the UI.
When calling deleteLastStroke() nothing happens. However when doing:

deleteLastStroke();
setState((){});

The UI rebuilds and everything works as expected.

If I am doing something wrong could you please point me in the right direction?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.