rodydavis / signals.dart

Reactive programming made simple for Dart and Flutter
http://dartsignals.dev
Apache License 2.0
378 stars 44 forks source link

Error while creating computed - Instance of 'EffectCycleDetectionError' #247

Closed 2shrestha22 closed 2 months ago

2shrestha22 commented 2 months ago

Getting this error while using createComputed.

flutter: computed created: [5|null]
Reloaded 1 of 731 libraries in 2,201ms (compile: 14 ms, reload: 156 ms, reassemble: 2017 ms).

════════ Exception caught by widgets library ═══════════════════════════════════
The following EffectCycleDetectionError was thrown building MyHomePage(dirty, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#add1b]], state: _MyHomePageState#53989):
Instance of 'EffectCycleDetectionError'

The relevant error-causing widget was:
    MyHomePage MyHomePage:file:///home/sangam/repo/signal/lib/main.dart:20:19

When the exception was thrown, this was the stack:
#0      Computed.value (package:signals_core/src/core/computed.dart:539:7)
computed.dart:539
#1      ElementWatcher.subscribeWatch.<anonymous closure> (package:signals_flutter/src/watch/element_watcher.dart:93:13)
element_watcher.dart:93
#2      Effect._callback (package:signals_core/src/core/effect.dart:350:32)
effect.dart:350
#3      new Effect (package:signals_core/src/core/effect.dart:328:7)
effect.dart:328
#4      effect (package:signals_core/src/core/effect.dart:591:20)
effect.dart:591
#5      ElementWatcher.subscribeWatch (package:signals_flutter/src/watch/element_watcher.dart:90:21)
element_watcher.dart:90
#6      ElementWatcher.watch (package:signals_flutter/src/watch/element_watcher.dart:54:7)
element_watcher.dart:54
#7      watchSignal (package:signals_flutter/src/watch/extension.dart:24:24)
extension.dart:24
#8      bindSignal (package:signals_flutter/src/core/signal.dart:29:3)
signal.dart:29
#9      bindComputed (package:signals_flutter/src/core/computed.dart:30:10)
computed.dart:30
#10     createComputed (package:signals_flutter/src/core/computed.dart:91:10)
computed.dart:91
#11     _MyHomePageState.activeTodo (package:signal/main.dart:38:27)
main.dart:38
#12     _MyHomePageState.activeTodo (package:signal/main.dart)
#13     _MyHomePageState.build (package:signal/main.dart:67:24)
main.dart:67
#14     StatefulElement.build (package:flutter/src/widgets/framework.dart:5592:27)
framework.dart:5592
#15     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5480:15)
framework.dart:5480
import 'package:flutter/material.dart';
import 'package:signal/todo_state.dart';
import 'package:signals/signals_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SignalsAutoDisposeMixin {
  final textEditingController = TextEditingController();
  late final todo = createSignal<List<Todo>>(context, []);

  late final activeTodo = createComputed(
    context,
    () => todo().where((it) => !it.completed),
  );
  late final completed = createComputed(
    context,
    () => todo.value.where((it) => it.completed),
  );

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
          bottom: const TabBar(
            tabs: [
              Tab(child: Text('Active')),
              Tab(child: Text('Completed')),
            ],
          ),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: TabBarView(
            children: [
              TodoListView(
                todos: activeTodo().toList(),
              ),
              TodoListView(
                todos: completed().toList(),
              )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            showDialog(
              context: context,
              builder: (context) {
                return Dialog(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        TextField(
                          controller: textEditingController,
                          autofocus: true,
                          maxLines: 4,
                          maxLength: 100,
                          decoration: const InputDecoration(
                            border: InputBorder.none,
                            hintText: 'Add your todo...',
                          ),
                        ),
                        const SizedBox(height: 8),
                        FilledButton(
                          onPressed: () {
                            todo.value.add(
                              Todo(1, textEditingController.text, false),
                            );
                            textEditingController.clear();
                          },
                          child: const Text('Save'),
                        )
                      ],
                    ),
                  ),
                );
              },
            );
          },
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

class TodoListView extends StatelessWidget {
  const TodoListView({super.key, required this.todos});
  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(todos[index].title),
        );
      },
    );
  }
}
rodydavis commented 2 months ago

Is it throwing an error just by running it?

2shrestha22 commented 2 months ago

Is it throwing an error just by running it?

Yes. If computed is present it throws error.

rodydavis commented 2 months ago

Found the bug and will patch soon. Also cleaned up the code to show how to use list signal:

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SignalsAutoDisposeMixin {
  final textEditingController = TextEditingController();
  late final todo = bindSignal(context, listSignal<Todo>([]));

  late final activeTodo = createComputed(
    context,
    () => todo().where((it) => !it.completed),
  );

  late final completed = createComputed(
    context,
    () => todo().where((it) => it.completed),
  );

  @override
  Widget build(BuildContext context) {
    // todo.watch(context);
    activeTodo.watch(context);
    completed.watch(context);
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
          bottom: const TabBar(
            tabs: [
              Tab(child: Text('Active')),
              Tab(child: Text('Completed')),
            ],
          ),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: TabBarView(
            children: [
              TodoListView(
                todos: activeTodo().toList(),
              ),
              TodoListView(
                todos: completed().toList(),
              )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            showDialog(
              context: context,
              builder: (context) {
                return Dialog(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        TextField(
                          controller: textEditingController,
                          autofocus: true,
                          maxLines: 4,
                          maxLength: 100,
                          decoration: const InputDecoration(
                            border: InputBorder.none,
                            hintText: 'Add your todo...',
                          ),
                        ),
                        const SizedBox(height: 8),
                        FilledButton(
                          onPressed: () {
                            todo.add(
                              (
                                1,
                                title: textEditingController.text,
                                completed: false
                              ),
                            );
                            textEditingController.clear();
                          },
                          child: const Text('Save'),
                        )
                      ],
                    ),
                  ),
                );
              },
            );
          },
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

class TodoListView extends StatelessWidget {
  const TodoListView({super.key, required this.todos});
  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(todos[index].title),
        );
      },
    );
  }
}

typedef Todo = (int, {String title, bool completed});