wwwdata / implicitly_animated_reorderable_list

Fork of the discontinued plugin to continue maintaining it
MIT License
30 stars 22 forks source link

MyersDiff fail when Having a big list #11

Closed Andrflor closed 1 year ago

Andrflor commented 1 year ago

It's working perfectly on small lists However on a 70+ items list I have an Issue with MyersDiff on both IOS and Android. This makes the list having hieratic behaviors.

Flutter version: 3.0.5 Dart version: 2.17.6

Stacktrace

Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:io' Class: _RandomAccessFileOpsImpl@13069316)
#0      Isolate._spawnFunction (dart:isolate-patch/isolate_patch.dart:399:25)
#1      Isolate.spawn (dart:isolate-patch/isolate_patch.dart:379:7)
#2      compute (package:flutter/src/foundation/_isolates_io.dart:35:19)
#3      MyersDiff.diff (package:animated_list_plus/src/diff/myers_diff.dart:47:21)
#4      MyersDiff.withCallback (package:animated_list_plus/src/diff/myers_diff.dart:24:12)
#5      ImplicitlyAnimatedListBaseState._calcDiffs (package:animated_list_plus/src/implicitly_animated_list_base.dart:182:19)
<asynchronous suspension>
wwwdata commented 1 year ago

Do you have some example to reproduce the problem?

Andrflor commented 1 year ago

Also found someone having the same issue reported here https://github.com/themadmrj/chat_ui_kit/issues/44

Minimal code

ImplicitlyAnimatedReorderableList<ShoppingModel>(
              items: shoppingList,
              areItemsTheSame: (oldItem, newItem) => oldItem.key == newItem.key,
              onReorderFinished: (item, from, to, newItems) =>
                  shoppingList = newItems,
              itemBuilder: (context, itemAnimation, item, index) {
                return Reorderable(
                  key: ValueKey(item),
                  builder: (context, dragAnimation, inDrag) {
                    return SizeFadeTransition(
                      sizeFraction: 0.7,
                      curve: Curves.easeInOut,
                      animation: itemAnimation,
                      child: Handle(
                        delay: const Duration(milliseconds: 100),
                        child: Container(
                          height: 55,
                          child: InkWell(
                            onTap: () {
                              shoppingList.removeAt(index);
                              setState((){});
                            },
                            child: Text(
                              item.name,
                            ),
                          ),
                        ),
                      ),
                    );
                  },
                );
              },
              shrinkWrap: true,
            ),
    );

ShoppingModel

class ShoppingModel {
    int key;
    String name;
}
wwwdata commented 1 year ago

Hm, so I was trying to reproduce the problem but so far I was not successful. Here is an example with a list that has 1000 items, and is also reorderable and has a short filter button that filters the list to only show even items... This all works fine for me without any exceptions... Could you maybe provide a concrete example that crashes?

import 'dart:ui';

import 'package:animated_list_plus/animated_list_plus.dart';
import 'package:animated_list_plus/transitions.dart';
import 'package:flutter/material.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(
        primarySwatch: Colors.blue,
      ),
      home: const ListTest(),
    );
  }
}

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

  @override
  State<ListTest> createState() => _ListTestState();
}

class _ListTestState extends State<ListTest> {
  final _controller = ScrollController();

  final items = List.generate(1000, (index) => index);
  late var filteredList = items;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('List test'),
        actions: [
          IconButton(
            onPressed: () {
              setState(() {
                if (filteredList.length < items.length) {
                  filteredList = items;
                } else {
                  filteredList =
                      items.where((element) => element.isEven).toList();
                }
              });
            },
            icon: const Icon(Icons.filter_list_rounded),
          )
        ],
      ),
      body: Scrollbar(
        controller: _controller,
        child: ImplicitlyAnimatedReorderableList(
          controller: _controller,
          items: filteredList,
          areItemsTheSame: (a, b) => a == b,
          onReorderFinished: (item, from, to, newItems) {
            setState(() {
              filteredList
                ..clear()
                ..addAll(newItems);
            });
          },
          itemBuilder: (context, animation, item, index) {
            return Reorderable(
              key: ValueKey(item),
              builder: (context, dragAnimation, inDrag) {
                final t = dragAnimation.value;
                final elevation = lerpDouble(0, 8, t)!;
                final color =
                    Color.lerp(Colors.white, Colors.white.withOpacity(0.8), t);

                return SizeFadeTransition(
                  sizeFraction: 0.7,
                  curve: Curves.easeInOut,
                  animation: animation,
                  child: Material(
                    color: color,
                    elevation: elevation,
                    type: MaterialType.transparency,
                    child: ListTile(
                      title: Text('item $item'),
                      trailing: const Handle(
                        delay: Duration(milliseconds: 100),
                        child: Icon(
                          Icons.list,
                          color: Colors.grey,
                        ),
                      ),
                    ),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}
wwwdata commented 1 year ago

Is your item class maybe large complicated one that cannot be sent to an isolate? If so there is a flag to disable isolate support. But better would be if you map it to a simple class for display purposes only then.

Andrflor commented 1 year ago

I may have too big objects. Just disabling the isolate is a cheap fix. It seems more a problem of the limitation coming from isolates than anything else.