drag-drop-flutter

Hello everyone 👋 In this article, I will explain how to perform Drag&Drop operations in Flutter.

Essentially, we use two main widgets for Drag&Drop operations.

Draggable: This widget represents the draggable part, where we define the widgets we want to drag.

DragTarget: This widget represents the area that can accept the draggable widget, allowing us to accept draggable widgets with necessary condition checks.

Drag&Drop generally provides convenience and ease of use in applications. For instance, if we want to create a sortable list by the user, we can do this quite easily using Draggable and DragTarget. Otherwise, we would have to rely on packages, which I prefer to avoid unless necessary. I believe most people feel the same way.

Let’s Get Started!

To analyze the widgets we will use for Drag & Drop, I created a scenario. It resembles those Barbie dress-up games 😂

drag-drop-flutter

We will have two different targets. One will target the upper part of the body, while the other will target the lower part, meaning we will put on a Tshirt for the upper body and Jean for the lower.

First, let’s define the body parts.

abstract class LowerBody {
  final String name;
  final String imageUrl;

  LowerBody(this.name, this.imageUrl);

  void wear() {
    print('$name worn - lower');
  }
}

Similarly, we define the upper body as UpperBody.

Now let’s create a few clothing items that will inherit from these classes. We will inherit the items for the upper body from UpperBody and for the lower body from LowerBody.

import 'upper_body.dart';

class Tshirt extends UpperBody {
  Tshirt(String name, String imageUrl) : super(name, imageUrl);
}

We will also create Hoodie from UpperBody and Jean, Trousers from LowerBody.

drag-drop-flutter

Now let’s create a mock repository.

import 'model/hoodie.dart';
import 'model/jean.dart';
import 'model/trousers.dart';
import 'model/tshirt.dart';

class ClothesRepository {
  Tshirt getTshirtById(int id) => Tshirt('Tshirt',
      'https://png.clipart.me/previews/b98/t-shirt-vector-template-illustrator-26174.png');

  Hoodie getHoodieById(int id) => Hoodie('Hoodie',
      'https://cdn.pixabay.com/photo/2013/07/12/15/54/sweater-150533_1280.png');

  Jean getJeanById(int id) => Jean('Jean',
      'https://images.vexels.com/media/users/3/142631/isolated/preview/c32fc2bd1003f31fff074fa2d30a21c8-simple-jean-clothing.png');

  Trousers getTrousers(int id) => Trousers('Trousers',
      'https://pixsector.com/cache/5b3790fa/av1ada5e8276b9ab6e4f5.png');
}

Now that our data is ready, we can create the interface and our Draggable widget.

Since we can place different types of clothing in the widget, we will create our widget as Generic. There are two types of Draggable: Draggable and LongPressDraggable. We can use whichever we prefer. Directly defining Draggable can cause issues when using Scroll, so in such cases, it’s better to opt for LongPress, as it allows us to set a delay for pressing.

The Draggable widget takes our main design widget in child, accepts a widget in feedback to modify the design during dragging, and in childWhenDragging, we can show a different design for the Draggable during dragging. The axis property of the Draggable widget allows us to determine the direction of dragging and restrict the movement direction.

import 'package:flutter/material.dart';

class DraggableWidget<T> extends StatelessWidget {
  final T model;
  final String title;
  final String url;
  const DraggableWidget({
    Key? key,
    required this.model,
    required this.title,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Draggable<Object>(
      feedback: _clothesImage(url),
      child: Column(
        children: [
          Text(title),
          _clothesImage(url),
        ],
      ),
      data: model,
    );
  }

  Widget _clothesImage(String url) => Image.network(url, width: 100, height: 100);
}

Lastly, we have functions that trigger events such as the beginning, ending, or canceling of the drag operation.

We put the model we sent inside the data field of the Draggable. This way, during drag events, we can access the data inside and process it with the DragTarget.

Now let’s create a Gardrobe widget using this DraggableWidget.

final Tshirt tshirt = _clothesRepository.getTshirtById(0);

DraggableWidget<Tshirt>(
    model: tshirt,
    title: tshirt.name,
    url: tshirt.imageUrl,
),

We create the example clothing items by retrieving them from the repository in this way.

drag-drop-flutter

Now that we have created our Gardrobe, let’s get to the DragTarget part. So far, we have created our draggable widgets. The area where we will drag them will be created by DragTarget. Let’s write our widget containing the targets.

drag-drop-flutter

DragTarget<LowerBody>(
    builder: (context, items, _) {
      return _clothesWidget(url: lowUrl);
    },
    onAccept: (item) {
      item.wear();
      lowUrl = item.imageUrl;
      setState(() {});
    },
    /* Optional checks for specificity can be added!
    onWillAccept: (data) {
        if (data is Trousers) {
            print('acceptable');
            return true;
        } else {
            print('unacceptable');
            return false;
        }
    }, */
    /* This triggers on all movements in the area!
    onMove: (details) => print(details.data.name),*/
    /* This triggers when leaving the area!
    onLeave: (data) => print(data?.name ?? ''),*/
),

We created our LowerBody DragTarget. In the onAccept section of the DragTarget, we perform the actions we want when an item is accepted. In this example, I retrieved the clothing’s imageUrl and placed it in the DragTarget view, allowing us to see the visual of the dragged Draggable widget on the [DragTarget](https://api.flutter.dev/flutter/widgets/Drag

Target-class.html). We should also create the UpperBody target similarly.

The Draggable widgets we created could accept different types of clothing. These types, inherited from UpperBody and LowerBody, allow the created DragTarget to accept subtypes like Hoodie, Tshirt for UpperBody and Jean, Trousers for LowerBody.

There are many checks available for DragTarget. Generally, we can use onWillAccept to perform necessary actions before dropping the dragged Draggable widget. While dragging the Draggable widget over the DragTarget, the Move function is continuously triggered, and when leaving the target area, we can observe the onLeave function being triggered. Since we may need to write specific actions for these, they are quite critical.

Additionally, we will write the clothesWidget used within.

Widget _clothesWidget({String? url}) => Container(
    width: 100,
    height: 100,
    decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.black),
    ),
    alignment: Alignment.center,
    child: url != null ? _clothesImage(url) : const SizedBox(),
);

Widget _clothesImage(String url) => Image.network(
    url,
    width: 100,
    height: 100,
);

We have created all the necessary areas. Now we can run our app and see what we have done.

Here’s Our Demo!

drag-drop-flutter

To summarize, we created two Targets and dragged different Draggable widgets to them, taking different actions. By accepting the Draggable widgets of its own type (inherited), it retrieved their imageUrl. When we dragged a Draggable of a different generic type, it was not accepted since it was not its own subclass.

I tried to explain the Drag & Drop event in detail, and I hope you found it engaging 🤓

Instead of sharing the code for the pages of the project directly, I preferred to share the key points, but you can find the entire source code of the project here.

Twitter: FurkayOlkay

Github: github.com/furkay