Flutter provides a variety of Widgets through which we can build any kind of UI. But more than a design, your app should be interactive so that it can be easily accessed to users. In this article, I will be building the following app which makes use of Draggable and DragTarget. So, first of all, let’s look at what are Draggable and DragTarget in Flutter.
As the name suggests, Draggable means a widget which can be dragged or moved on the screen. Let’s take an example :
Here’s the code for this:
Draggable class generally made of four main parameters :
- child: Widget which can be dragged or to be displayed when draggable is stationary.
- feedback: Widget to be displayed when drag is underway.
- childWhenDragging: Widget to be displayed in the original place of Draggable when drag is underway.
- data: It contains the information dropped by Draggable at DragTarget, it is useful when we are using multiple Draggable and DragTarget.
The draggable class provides some callback functions, such as :
- onDragStarted: Called when the Draggable widget starts to drag.
- onDragCompleted: Called when Draggable is dropped on DropTarget and DropTarget has accepted the Draggable.
- onDragEnd: Called when Draggable is dropped on DropTarget. This contains the velocity and Offset of Draggable when it was dropped.
- onDraggableCancelled: Called when Draggable is dropped but not accepted by DragTarget.
As the name suggests, DragTarget means a widget which provides target or destination to Draggable, it also receives the data from a Draggable. Let’s take an example :
Here’s the code for this :
- In line 4, if the value of isSuccessful is true then Container with FlutterLogo child will be returned, else empty Container will be returned.
- In line 24, onWillAccept will contain the data of a Draggable, if you want your DragTarget to accept the data of Draggable then return true like in this case. After this control will be transferred to the onAccept property of DragTarget (line 27).
- In line 27, inside onAccept I am changing the value of isSuccessful to true, as the Draggable is dropped on DragTarget and data is accepted by DragTarget.
DragTarget class generally made of four main parameters:
Takes a function which is called to build the contents of DragTarget, we can build different widgets based on Draggable. This takes three parameters:
- BuildContext: Context of a Widget.
- candidateData: It contains the list of draggable data which will be accepted by DragTarget.
- rejectedData: It contains the list of Draggable data which will not be accepted by DragTarget.
Takes a function which provides the data of Draggable to use as a parameter and returns bool based on whether Draggable will be accepted or not, if onWillAccept returns true then onAccept is called.
Takes a function which provides the data of Draggable which was accepted by DragTarget.
Called when Draggable leaves the DragTarget and not dropped into DragTarget.
I hope your basics of Draggable and DragTarget are clear now. Let’s move to our demo app which I will be building using these two widgets.
I’ll create a simple app which contains the stack of cards as a Draggable and user has to drop that card to DragTarget until the stack becomes empty, also there is a Reset button through which app can be reset again.
- I’ll be building this app using Provider architecture, so you must know the concept of Provider and how it works.
- Basics of Draggable and DragTarget which were covered earlier in this blog.
Enough of theory, let’s get started.
First, I’ll create a basic structure of the App. Our app contains two screens — SplashScreen and HomePage.
Open main.dart file and add the following code.
- At line 12, HomePage is wrapped by ChangeNotifierProvider, it listens to ChangeNotifier, pass the value to its descendants and rebuilds the descendants whenever notifyListeners() get called.
- ChangeNotifierProvider takes a builder function basically which is used to initialize our data part of our app which can be changed.
Now, Create five files mentioned below :
- data.dart: Contains business logic.
- cardItem.dart : a model class for a single item in the list of Draggable.
- constants.dart: Contains some static data or constants.
- strings.dart: Contains some strings which I’ll be using in this app.
- colors.dart: Contains some colors which I’ll be using in this app.
Open colors.dart and add the following code.
Open strings.dart and add the following code.
Open cardItem.dart and add the following code. It contains some basic information about a single item in the list.
Open constants.dart and add the following code. It contains variables for routes and some static content for the list items.
Open data.dart and add the following code.
- At line 1, I extended our class with ChangeNotifier so that it can listen for changes.
- Then, I have created some variables like successDrop (used to check whether Draggable is dropped into DragTarget or not), items (list of items which will be shown as Draggables ) and acceptedData (data accepted by DragTarget from Draggable).
- At line 6, inside the constructor, initialize the list and variables.
- Then I have created some getters and setters for the variables and inside setter functions notifyListeners() will be called so that the Widgets listening for these variables can rebuild.
- At line 30, removeLastItem() method is created which will remove the last item from the list of Draggable.
- At line 35, addItemToList() method is created which will add the particular CardItem into the list of DragTarget.
Now, open home.dart which will contain the HomePage of the app, and add the following code.
- It contains FAB for Reset functionality and the Column widget at the center of screen having two childs : CardStackWidget() and DragTargetWidget() .
- CardStackWidget (contains a list of Draggable) and DragTargetWidget (as the name suggests, contains DragTarget) I’ll be building later in this blog, Let me first explain this code a little bit.
- At line 10, inside onPressed of FAB, I am calling initializeDraggableList() function which is present in data.dart class, so that our app can change to reset mode.
- At line 11, I am calling changeSuccessDrop() function which is present inside data.dart as well, and passing the false value as initially no items were dropped on DragTarget.
Now, create a new file named cardStackWidget.dart and inside this add the following code.
- Firstly, you will see the errors in line 29 because of DraggableWidget class, I’ll build this class later, but for now, let’s understand this code.
- This widget contains the Stack and inside the children property, I am calling the function named cardItems() which returns the list of Widgets based on some condition.
- At line 13, if the length of the list containing Draggable is less than 1 or empty it will return the Container showing ‘No Items Left’ so that it cannot be further dragged and user get to know about the end of the list.
- At line 26, if the length is more than or equal to 1 then loop through the elements of the list and pass the particular item to DraggableWidget() which I’ll build later in this blog.
- After this add the value of cardItemDraggable to list of widgets and return that list to the children of Stack.
Now create a new file named draggableWidget.dart and inside this add the following code.
- Firstly, this file is only responsible for building the Draggable widget for the particular index of CardItem list that I passed from CardStackWidget, so don’t get confused, I’ll explain the whole code.
- At line 16, I passed the single item of list i.e single CardItem as the data of Draggable.
- When the card is dragging, the previous card should be displayed in place of that card, so I’ll be doing this inside childWhenDragging property.
- At line 24, for the color of the card if the index is more than or equal to 1 then color should be shown of the previous card that’s why I have written “(itemList.length-1)-1” and if the value of the index is less than 1 or 0 then simply set the grey color for the card.
- At line 33 , same logic is applied for the content of the card if the index is more than or equal to 1 then set the content of previous card (this is done using elementAt() method, it returns the value at the index passed in the parameter ) in the Text widget and if the value of index is less than 1 or 0 then simply set the text as “No items left” .
- At line 41, for feedback property, card at the index which I am getting from cardStackWidget is returned.
- At line 56, for child property, card at the index which I am getting from cardStackWidget is returned.
- Just for reminding: Data of Draggable can be accessed inside onWillAccept and onAccept property of DragTarget which will be present inside DragTargetWidget class and I have not built this class yet.
In our column, there were two children- CardStackWidget and DragTargetWidget and inside CardStackWidget there is DraggableWidget, I have built CardStackWidget and DragTargetWidget till now, Now it’s time to build DragTargetWidget.
Create new file named dragTargetWidget.dart and add the following code.
- This class is only responsible for building DragTarget. I’ll explain this whole code.
- Firstly, I’ll take a look at builder function, at line 21, if the value of successDrop is false then simply return card having text “Drop Items here” wrapped around DottedBorder.
- At line 4, inside onWillAccept function, return true, so that control gets transferred to onAccept function.
- At line 7, the inside onAccept function I am calling three methods because :
- 1) removeLastItem(): When Draggable is dropped on DragTarget then the last item from the list of Draggable should be removed.
- 2)changeSuccessDrop(): When Draggable is successfully dropped on DragTarget the value of successDrop should be changed from false to true.
- 3) changeAcceptedData() : Data accepted by DragTarget should be changed.
- Now, at line 13 inside builder function I am checking if the value of successDrop is true then simply return the Stack containing the list of DragTarget as a children which are provided by a method called buildTargetList() taking the acceptedData as a parameter.
- At line 51, inside buildTargetList() function simply add a card wrapped around DottedBorder containing the content of acceptedData to the list.
That’s the end of the article, you can run the app on your device or emulator to see the output.
Link of the code :
A flutter sample app to showcase drag-drop functionality in flutter. - flutter-devs/flutter_dragdrop_demo
Interesting part of this app is , I am not using Stateful Widget , instead I am using Provider package.
I got something wrong? Mention it in the comments. I would love to improve.
If you learnt even a thing or two, clap your hands 👏 as many times as you can to show your support!
Connect with me on LinkedIn.
Check my GitHub repositories.
Follow me on Twitter.