letsar / flutter_staggered_grid_view

A Flutter staggered grid view
MIT License
3.12k stars 508 forks source link

`MasonryGridView.custom` doesn't handle `findChildIndexCallback` properly #340

Open jellynoone opened 6 months ago

jellynoone commented 6 months ago

Passing a custom SliverChildBuilderDelegate that uses a findChildIndexCallback to the MasonryGridView.custom constructor and changing the items order causes the package to fail.

Consider the following example:

import 'dart:math';

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

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MasonPage(),
    );
  }
}

class MasonPage extends StatefulWidget {
  const MasonPage({super.key});

  @override
  State<MasonPage> createState() => _MasonPageState();
}

class _MasonPageState extends State<MasonPage> {
  final Random _rng = Random(0);
  late final _data = List.generate(200, (index) {
    final height = 100 + (_rng.nextDouble() * 100);

    return (
      id: '$index',
      color: Color.fromRGBO(0, 0, 0, height / 200),
      height: height,
    );
  });

  void _randomize() {
    setState(() {
      _data.shuffle(_rng);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MasonryGridView.custom(
        gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 100,
        ),
        childrenDelegate: SliverChildBuilderDelegate(
          _buildItem,
          childCount: _data.length,
          findChildIndexCallback: _findChildIndex,
        ),
      ),
      floatingActionButton: FloatingActionButton.small(
        onPressed: _randomize,
        child: const Icon(Icons.replay),
      ),
    );
  }

  Widget _buildItem(BuildContext context, int index) {
    final item = _data[index];

    return SizedBox(
      key: Key(item.id),
      height: item.height,
      child: ColoredBox(
        color: item.color,
      ),
    );
  }

  int? _findChildIndex(Key key) {
    if (key is! ValueKey) return null;

    final index = _data.indexWhere((e) => e.id == key.value);
    if (index < 0) return null;

    return index;
  }
}

Clicking on the floating button to shuffle the backing items list produces an error. Commenting out: findChildIndexCallback: _findChildIndex, and repeating the same steps, however, produces the right results.

It seems the masonry grid implementation is not accounting for the potential Widget-RenderObject reparenting.

jellynoone commented 5 months ago

@letsar Did you perhaps have a chance to look at this?