Gunshots App

I’ll try to publish a new article every week. See you next time. 🤓

Yup, that is something I said on Feb 9th. I am way too good with keeping my word. Therefore I take this opportunity to give myself a 2 article per week challenge till the end of WFH days. Let’s see how bad I score on this one.

The app that we will build is primarily a learning activity for beginners. We will be learning many different concepts in this one —

Loading images from assets Playing audio from assets Using Stack and Positioned widgets Learning very specific things about the behaviors of Container constraints.

This app is more like an incremental project. I will keep keep adding things to this app and will keep updating you guys with my writings. So let’s get into it.

Before jumping into code we should always take a step back and think upon how are we going to build it. A small initial plan in your mind containing the basic layouts, how the state will change, what events will update the UI and so on. Let me share with you my thought process —

Hmmmm, so of course the idea is to display a gunshot mark wherever I click on the screen. For the gunshot mark I can use an image. Also, to update the UI, I will need to rebuild my widget inside which the images will be displayed. That widget should cover the whole screen. So every tap by the user results in a rebuild of this screen sized widget, which has these gunshot images as it’s children. Okay, let’s begin then.

What is a widget that can expand to max size possible, have multiple children and you can place those children at exact locations within it, without caring about the axis or directions?

That is possible with a combination of Stack and Positioned widgets.

Let’s see what the documentation has to say for these two dudes —

Stack — A widget that positions its children relative to the edges of its box. This class is useful if you want to overlap several children in a simple way, for example having some text and an image, overlaid with a gradient and a button attached to the bottom. Positioned — A widget that controls where a child of a Stack is positioned.

So now we know how to position the images inside the Stack . We will use the top , left , bottom and right properties of the Positioned widget to position our images inside the Stack.

But, how does the stack occupy the whole screen area?

The Stack has a property called the fit property. It is responsible for passing the constraints from the parent to the Stack. What this basically means is that, how should the Stack size itself to the available space. Should the Stack take the minimum space it needs or should the Stack just occupy all the space there is. If the fit property is set to StackFit.expand , then the stack occupies all the space that it can.

Okay, we know how to position our image inside the Stack, we know how to make the Stack take all the available space but now, how do I load the gunshot mark image?

I chose the image from freepngimg.com.

Loading an image from assets is pretty straight forward method.

First, create the following path assets/images/ and add the gunshot.png to it. Then add assets/images/gunshot.png under the assets node in pubspec.yaml file. Now, we are all set to use the image. We will take help of the Image (Image.asset constructor) widget to load the image.

So this is how our code should look like if we pant to position one image —

This is how the above code looks like on your phone.

One thing that you would notice is the fit property of the Image. BoxFit.cover sizes the image in a way in which it tries to be as big as possible while maintaining the aspect ratio within the given constraints.

Well okay, I get all of this, but how do I add images on every screen tap?

All we have to do is to add more children to the Stack and just rebuild the Stack. To rebuild the Stack we can call the setState() method. The setState() method rebuilds the widget it is called from.

To add children to the Stack, we can just create a list, assign that list to the children field, and just keep appending the Image to it whenever a screen tap happens.

Okay, so how do I get to know about a screen tap? And how do I know the location at which the tap event occurred?

GestureDetector is the widget you are looking for. It even provides you with the TapDetails . TapDetails contain global as well as relative tap coordinates.

Ummmm, can you show it in code please?

Here it is —

Couple of things to notice in this code —

I have used onTapDown property instead of onTap property. This is because the TapDetails is available for the onTapDown callback and not onTap callback. The top and left values have been subtracted by 25.0. This is because the top left corner of the Image will be at the point specified by the Positioned widget. So to place the image at the center of the tap, we need to set the values for the Positioned widget in such a way that the center of the Image lies on top of the tap. This code doesn’t work as expected. If you try to tap on the screen, there will be no response. This is happening because when the Stack gets created the very first time, it has no children . So, there is nothing to render inside the Stack . And the way many widgets work in Flutter is, they need to have a child or a property so that they can be sized according to max constraints. Now, the Container is also such widget, but passing a color to the Container which is inside a Stack allows the Container to be rendered with max available constraints.

Hence, the code changes a little bit for it to work. Just add a Container to the empty _gunshots list.

@override

void initState() {

super.initState();

this._gunshots = <Widget>[

Container(color: Colors.white,)

];

}

Hmmm, I guess now what we are left with is the sound part?

Yes, the sound part again is similar to the way we did the Image loading part. But this time, we will need a dependency to ease our work.

We will be using the audioplayers dependency. Firstly, we will create an assets/sounds/ path and add our gunshot.mp3 file to it. Then we create an AudioCache object in our code and just call the play method whenever we need to play the audio. Just be careful while specifying the path to the audiofile in the play method, by default it adds a assets/ prefix.

So here is the final code —