flutter-plugins icon indicating copy to clipboard operation
flutter-plugins copied to clipboard

[desktop_drop] The function of `onDrag*` would execute multiple times when current widget is not root route

Open honmaple opened this issue 1 year ago • 3 comments

When using Navigator.of(context).push(...) to another route, desktop_drop would execute multiple times. Here is my test code

import 'package:flutter/material.dart';
import 'package:desktop_drop/desktop_drop.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(
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: MyDragWidget(
        title: "home",
        child: Center(
          child: TextButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => const MySecondPage()),
              );
            },
            child: Text("to second page"),
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Page"),
      ),
      body: MyDragWidget(
        title: "second",
        child: Center(
          child: TextButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => const MyThirdPage()),
              );
            },
            child: Text("to third page"),
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Third Page"),
      ),
      body: MyDragWidget(
        title: "third",
        child: Center(
          child: Text("third page"),
        ),
      ),
    );
  }
}

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

  final String title;
  final Widget child;

  @override
  State<MyDragWidget> createState() => _MyDragWidgetState();
}

class _MyDragWidgetState extends State<MyDragWidget> {
  bool _dragging = false;

  @override
  Widget build(BuildContext context) {
    return DropTarget(
      onDragDone: (detail) async {
        print("${widget.title} done");
      },
      onDragEntered: (detail) {
        setState(() {
          _dragging = true;
        });

        print("${widget.title} entered");
      },
      onDragExited: (detail) {
        setState(() {
          _dragging = false;
        });

        print("${widget.title} exited");
      },
      child: _dragging
          ? Container(
              width: double.infinity,
              height: double.infinity,
              color: Colors.black.withOpacity(0.2),
              child: Center(
                child: Text("Drag Files"),
              ),
            )
          : widget.child,
    );
  }
}

Reproduce Steps

When in MyHomePage,the output is

flutter: home entered
flutter: home exited
flutter: home done

When in MySecondPage,the output is

flutter: home entered
flutter: second entered
flutter: home exited
flutter: home done
flutter: second exited
flutter: second done

When in MyThirdPage,the output is

flutter: home entered
flutter: second entered
flutter: third entered
flutter: home exited
flutter: home done
flutter: second exited
flutter: second done
flutter: third exited
flutter: third done

Expected behavior

The function of onDrag* only execute once whatever current widget is root route or not.

Version (please complete the following information):

  • Flutter Version: 3.27.3
  • OS: MacOS
  • plugin: desktop_drop: ^0.5.0

honmaple avatar Feb 02 '25 11:02 honmaple

i facing the same issue, and the way i avoid this is create a enable variable, then disable that variable when you push a new route

ImNotTurtle avatar Feb 05 '25 03:02 ImNotTurtle

I'm also facing this very issue.

here is how i handle this issue:

in my class define a drop state variable:

bool _dropEnable = true; ... some code ...

Widget build(BuildContext context) {

return Scaffold(
  body: DropTarget(
    enable: _dropEnable,
    ... some UI code...
}

.. then some UI call this function to show new screen
void showAddScreen() async{
    setState(() => _dropEnable = false);

    .. await Navigator.push ...

    
    setState(() => _dropEnable = true);
}

and this works perfectly fine to me

ImNotTurtle avatar Feb 14 '25 15:02 ImNotTurtle

I was able to workaround it by creating the following component that uses the visibility_detector package:

import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:visibility_detector/visibility_detector.dart';

// This class fixes the following issue of desktop_drop's package:
//  - https://github.com/MixinNetwork/flutter-plugins/issues/389
class FixedDropTarget extends StatefulWidget {
  const FixedDropTarget({
    super.key,
    required this.child,
    this.onDragEntered,
    this.onDragExited,
    this.onDragDone,
    this.onDragUpdated,
    this.enable = true,
  });

  final Widget child;
  final OnDragCallback<DropEventDetails>? onDragEntered;
  final OnDragCallback<DropEventDetails>? onDragExited;
  final OnDragCallback<DropEventDetails>? onDragUpdated;
  final OnDragDoneCallback? onDragDone;
  final bool enable;

  @override
  State<FixedDropTarget> createState() => _FixedDropTargetState();
}

class _FixedDropTargetState extends State<FixedDropTarget> {
  final _visibilityDetectorKey = UniqueKey();

  bool _isVisible = false;

  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
      key: _visibilityDetectorKey,
      child: DropTarget(
        onDragEntered: widget.onDragEntered,
        onDragExited: widget.onDragExited,
        onDragDone: widget.onDragDone,
        onDragUpdated: widget.onDragUpdated,
        enable: widget.enable && _isVisible,
        child: widget.child,
      ),
      onVisibilityChanged: (info) {
        final newIsVisible = info.visibleFraction > 0;
        if (newIsVisible != _isVisible) {
          setState(() {
            _isVisible = newIsVisible;
          });
        }
      },
    );
  }
}

augustocarmo avatar Feb 14 '25 20:02 augustocarmo