Hire the author: Dennis M

Pull Layout Android Image Source unsplash.com.

Here is the GitHub link for this project double-pull-android.

Introduction

In this article, you will learn how to implement quickly and simply a working double pull Layout feature in your app using the Scroll Adapter in Android. The first section of this project involves scrolling down the screen to display what is below the partial header. The second part involves scrolling up the screen so that the partial header disappears and the whole screen becomes visible. Therefore, it’s a double layout since you will be able to pull the screen both up and down enabling you to view both layouts simultaneously.

This project provides insight into the functionality of dual drag editing on android. In addition, the user gains a better understanding of the internal machinery that runs after using those features. This article expects you to have some experience in building Android apps in Kotlin

What motivates me to do this work is “How to create a double pull layout in Android studio”? I have created an Android app that uses the dual drag design required by users to connect to the system. In conclusion, this blog post will provide you with direct clues on how to use dual drag architecture on Android.

Photo Preview

Glossary

ScrollView is a viewing group that allows the management category placed within it to be scripted. Scroll views can have one straight child embedded in it. To add more views within a scroll view, create a direct child that adds a view group, for example, LinearLayout, and then add additional views within that Line. Scroll view only supports vertical scrolling. To scroll horizontally, use HorizontalScrollView instead. Never add RecyclerView or ListView to the scrolling view. Doing so causes malfunctioning of the user interface and poor user experience.

RecyclerView makes it easy to accurately display large data sets. You submit information and explain what each item looks like, and the RecyclerView library does things as fast as it can. As the name implies, RecyclerView updates those individual items. When an object scratches off the screen, RecyclerView does not damage its view. Instead, RecyclerView uses the new scratch-screen viewing. This reuse improves performance, improves the performance of your app, and reduces power consumption.
Frame Layout is a viewing group that allows a segment of viewers placed within it to be scanned. Scroll views can have one straight child embedded in it. To add multiple views within a scroll view, create a direct child that adds a view group, for example, LinearLayout, and add additional views to that LinearLayout

Steps:

Step 1: Adding required dependencies

Firstly, go to the app-level build.gradle file and add the following dependency:

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
implementation "androidx.constraintlayout:constraintlayout:${versions.constraintLayout}"
implementation "androidx.appcompat:appcompat:${versions.supportLibrary}"
implementation "com.google.android.material:material:${versions.supportLibrary}"
implementation project(':double-pull-delegate')
}
view raw build.gradle hosted with ❤ by GitHub

Secondly, the build.gradle file will be as shown below:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
applicationId "com.dennis.doublepull"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
implementation "androidx.constraintlayout:constraintlayout:${versions.constraintLayout}"
implementation "androidx.appcompat:appcompat:${versions.supportLibrary}"
implementation "com.google.android.material:material:${versions.supportLibrary}"
implementation project(':double-pull-delegate')
}
view raw build.gradle hosted with ❤ by GitHub

Step 2: Creating the activity_main.xml file

Now, come to your activity_main.xml file which is responsible for designing the layout of the app and it’s located in the layout folder under the res folder. Change your default to Relative Layout then add additional attributes like this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/main_in"/>
<include layout="@layout/main_out"/>
</RelativeLayout>

As shown above, the activity_main.xml is made up of two layouts which are main_in.xml and main_out.xml. Go to the layout folder in the res folder and create a new layout called main_in.xml and add the following code as shown below.

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_rcv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_alignParentBottom="true"
android:background="#aa000000"
android:gravity="center">
<ImageButton
android:id="@+id/main_open_iv"
android:background="@null"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:src="@drawable/open" />
</RelativeLayout>
</merge
view raw main_in.xml hosted with ❤ by GitHub

Create another layout called main_out.xml and add the following code as shown below.

<?xml version="1.0" encoding="utf-8"?>
<dennis.pull.widget.PullScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<dennis.pull.widget.HeaderRelativeLayout
android:id="@+id/main_header"
android:layout_width="match_parent"
android:layout_height="275dp">
<ImageView
android:id="@+id/main_header_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/header2" />
<ImageView
android:id="@+id/main_header_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/bk_vediomask_bottom" />
</dennis.pull.widget.HeaderRelativeLayout>
<dennis.pull.widget.BodyRelativeLayout
android:id="@+id/main_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="200dp">
<RelativeLayout
android:id="@+id/main_content"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="1000dp"
android:layout_marginTop="75dp">
<TextView
android:textStyle="bold"
android:textColor="#bb000000"
android:textSize="20sp"
android:text="This is UP"
android:layout_marginTop="120dp"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:textStyle="bold"
android:textColor="#bb000000"
android:textSize="20sp"
android:text="This is CENTER"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:textStyle="bold"
android:textColor="#bb000000"
android:textSize="20sp"
android:text="This is BOTTOM"
android:layout_marginBottom="150dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<ImageView
android:id="@+id/main_haibao"
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="15dp"
android:scaleType="centerCrop"
android:src="@drawable/haibao2" />
<LinearLayout
android:id="@+id/main_info"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_toRightOf="@id/main_haibao"
android:orientation="vertical"
android:paddingLeft="20dp">
<LinearLayout
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="18dp"
android:layout_height="14dp"
android:scaleType="centerCrop"
android:src="@drawable/rating_bigger_select" />
<ImageView
android:layout_width="18dp"
android:layout_height="14dp"
android:scaleType="centerCrop"
android:src="@drawable/rating_bigger_select" />
<ImageView
android:layout_width="18dp"
android:layout_height="14dp"
android:scaleType="centerCrop"
android:src="@drawable/rating_bigger_select" />
<ImageView
android:layout_width="18dp"
android:layout_height="14dp"
android:scaleType="centerCrop"
android:src="@drawable/rating_bigger_select" />
<ImageView
android:layout_width="18dp"
android:layout_height="14dp"
android:scaleType="centerCrop"
android:src="@drawable/rating_bigger_select" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:gravity="center"
android:text="9.9"
android:textColor="#ff5021"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="A KENYAN FILM"
android:textColor="@android:color/white"
android:textSize="21sp" />
</LinearLayout>
</dennis.pull.widget.BodyRelativeLayout>
</RelativeLayout>
</dennis.pull.widget.PullScrollView>
view raw main_out.xml hosted with ❤ by GitHub

Step 3: Creating the ScrollAdapter.kt file

ScrollAdapter will use the onCreateViewHolder which creates another viewer as no other RecyclerView holders can reuse it. This way, for example, if your RecyclerView can display 5 items at a time, it will create 5-6 ViewHolders, and then reuse them, each time you run to BindViewHolder.

In RecyclerView – you don’t need to compress such a large amount by reusing ViewHolders as you do with ListView. The con is, RecyclerView is truly customizable, however, it is not a complete help – it is not at all like ListView is not completely customizable, but it has great basic advantages.

package com.dennis.doublepull.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.dennis.doublepull.R
class ScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_HEADER) {
HeaderViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.header, parent, false))
} else ScrollViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item, parent, false))
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
TYPE_HEADER
} else TYPE_NORMAL
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// do nothing
}
override fun getItemCount(): Int {
return DEFAULT_SIZE
}
private inner class ScrollViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private inner class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
companion object {
private const val TYPE_NORMAL = 1000
private const val TYPE_HEADER = 2000
private const val DEFAULT_SIZE = 31
}
}

Step 4: Creating the ScrollViewDelegate.kt file

You will use the setPullRelativeLayoutState function to make the scroll view work effectively as shown below

package dennis.pull.delegate
import android.view.MotionEvent
import android.view.View
import dennis.pull.ScrollState
import dennis.pull.listener.OnScrollChangedListener
class ScrollViewDelegate {
var scrollState = ScrollState.SHOW
private set
private var mOnScrollChangedListener: OnScrollChangedListener? = null
var downY: Int = 0
private set
var moveY: Int = 0
private set
fun setPullRelativeLayoutState(state: ScrollState) {
scrollState = state
}
fun onScrollChanged(view: View, l: Int, t: Int, oldl: Int, oldt: Int) {
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener!!.onScrollChange(view, l, t, oldl, oldt)
}
}
fun onInterceptTouch(ev: MotionEvent) {
when (ev.action) {
MotionEvent.ACTION_DOWN -> downY = ev.rawY.toInt()
MotionEvent.ACTION_MOVE -> moveY = ev.rawY.toInt()
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
downY = 0
moveY = 0
}
}
}
fun setOnScrollChangedListener(listener: OnScrollChangedListener) {
mOnScrollChangedListener = listener
}
}

Step 5: Creating the PullScrollView.kt file

The PullScrollView is only able to work by linking it to the Scrollview above and by using the several functions in place.

package dennis.pull.widget
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
import dennis.pull.ScrollState
import dennis.pull.delegate.ScrollViewDelegate
import dennis.pull.listener.OnScrollChangedListener
class PullScrollView : ScrollView {
private var mScrollViewDelegate: ScrollViewDelegate? = null
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr) {
init()
}
private fun init() {
mScrollViewDelegate = ScrollViewDelegate()
}
fun setPullRelativeLayoutState(state: ScrollState) {
mScrollViewDelegate!!.setPullRelativeLayoutState(state)
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
mScrollViewDelegate!!.onScrollChanged(this, l, t, oldl, oldt)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
mScrollViewDelegate!!.onInterceptTouch(ev)
if (mScrollViewDelegate!!.moveY - mScrollViewDelegate!!.downY < 0) {
return super.onInterceptTouchEvent(ev)
}
if (scrollY == 0) {
val state = mScrollViewDelegate!!.scrollState
if (state === ScrollState.SHOW || state === ScrollState.MOVE) {
return false
}
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
return if (mScrollViewDelegate!!.scrollState === ScrollState.HIDE) {
false
} else super.onTouchEvent(ev)
}
fun setOnScrollChangedListener(listener: OnScrollChangedListener) {
mScrollViewDelegate!!.setOnScrollChangedListener(listener)
}
}

Step 6: Creating the ScrollHeaderDelegate.kt file

You will use the ScrollShow set functions as shown below so as to enable the use of Header movement in the android application.

package dennis.pull.delegate
import android.view.View
class ScrollHeaderDelegate(private val mTargetView: View) : ScrollerDelegate(mTargetView) {
var isScrollShow: Boolean = false
private var mDuration = NORMAL_DURATION
fun scrollShow() {
if (!isScrollShow) {
return
}
val height = mTargetView.measuredHeight
if (height <= 0) {
return
}
smoothScrollTo(0, height, 0, -height, mDuration)
isScrollShow = false
}
fun setDuration(duration: Int) {
mDuration = duration
}
companion object {
private val NORMAL_DURATION = 800
}
}

Step 7: Creating the ScrollBodyDelegate.kt file

You will use the onTouchEvent function as shown below which is responsible for making objects move according to a preset program like the rotating triangle.

package dennis.pull.delegate
import android.view.MotionEvent
import android.view.View
import dennis.pull.ScrollState
import dennis.pull.listener.OnStateChangeListener
class ScrollBodyDelegate(private val mTargetView: View) : ScrollerDelegate(mTargetView) {
private var mMaxOffset: Int = 0
private var mLastY: Float = 0.toFloat()
private var mMoveY: Int = 0
var state = ScrollState.SHOW
private var mOnStateChangeListener: OnStateChangeListener? = null
fun setMaxOffset(offset: Int) {
mMaxOffset = offset
}
fun onTouchEvent(event: MotionEvent): Boolean {
if (state === ScrollState.HIDE) {
return false
}
val y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> mLastY = y
MotionEvent.ACTION_MOVE -> {
val moveY = (y - mLastY).toInt()
if (mTargetView.scrollY <= 0 && moveY > 0) {
val offset = moveY / 2
move(offset)
}
mLastY = y
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> changeState()
}
return true
}
private fun move(offset: Int) {
state = ScrollState.MOVE
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewMove(state, -offset)
}
mTargetView.scrollBy(0, -offset)
}
private fun hide() {
state = ScrollState.HIDE
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewHide(state)
}
mMoveY = mTargetView.measuredHeight + Math.abs(mTargetView.scrollY)
smoothScrollTo(0, mTargetView.scrollY, 0, -mMoveY, NORMAL_TIME * 3)
}
fun hide(time: Int) {
state = ScrollState.HIDE
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewHide(state)
}
mMoveY = mTargetView.measuredHeight + Math.abs(mTargetView.scrollY)
smoothScrollTo(0, mTargetView.scrollY, 0, -mMoveY, time)
}
private fun show() {
state = ScrollState.SHOW
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewShow(state)
}
smoothScrollTo(0, mTargetView.scrollY, 0, -mTargetView.scrollY,
mTargetView.scrollY)
}
private fun changeState() {
if (Math.abs(mTargetView.scrollY) > mMaxOffset + 50) {
hide()
} else {
show()
}
}
fun open() {
state = ScrollState.OPEN_START
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewOpenStart()
}
smoothScrollTo(0, -mMoveY, 0, mMoveY, NORMAL_TIME)
mTargetView.postDelayed({
state = ScrollState.OPEN_FINISH
if (mOnStateChangeListener != null) {
mOnStateChangeListener!!.pullViewOpenFinish()
}
}, NORMAL_TIME.toLong())
}
fun setOnStateChangeListener(listener: OnStateChangeListener) {
mOnStateChangeListener = listener
}
companion object {
private val NORMAL_TIME = 600
}
}

Step 8: Creating the MainActivity.kt file

Now, come to your MainActivity.java file. We will announce a function called initData () to display the Building Manager and Layout Adapter. Another important function that you will use in the main function is the initListener function. It is responsible for global app planning and multi-component mobility within the app.

Finally, your final MainActivity.java will be like this.

package com.dennis.doublepull
import android.graphics.drawable.AnimationDrawable
import android.os.Bundle
import android.view.View
import android.view.ViewTreeObserver
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.dennis.doublepull.adapter.ScrollAdapter
import kotlinx.android.synthetic.main.main_in.*
import kotlinx.android.synthetic.main.main_out.*
import dennis.pull.ScrollState
import dennis.pull.listener.OnScrollChangedListener
import dennis.pull.listener.OnStateChangeListener
class MainActivity : AppCompatActivity(), OnStateChangeListener,
View.OnClickListener, OnScrollChangedListener {
private var mHeaderHeight: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData()
initListener()
}
private fun initData() {
main_rcv.layoutManager = LinearLayoutManager(this)
main_rcv.adapter = ScrollAdapter()
}
private fun initListener() {
main_body.setOnStateChangeListener(this)
main_root.setOnScrollChangedListener(this)
main_open_iv.setOnClickListener(this)
main_rcv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (main_body.state === ScrollState.HIDE) {
main_root.scrollBy(dx, dy)
}
}
})
main_body.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
mHeaderHeight = main_header.measuredHeight
val iconHeight = main_haibao.measuredHeight
val pullRelMarTop = mHeaderHeight - iconHeight / 2
setPullRelativeLayoutMarTop(pullRelMarTop)
setContentViewMarTop(iconHeight / 2)
main_body.setMaxOffset(iconHeight / 2)
initOpenAnim()
main_body.viewTreeObserver.removeGlobalOnLayoutListener(this)
}
})
}
private fun initOpenAnim() {
main_header.isScrollShow = true
main_body.hide(100)
main_body.open()
main_header.scrollShow()
val animationDrawable = main_open_iv.drawable as AnimationDrawable
animationDrawable.start()
}
private fun setPullRelativeLayoutMarTop(top: Int) {
val mPullLayoutParams = main_body.layoutParams as RelativeLayout.LayoutParams
mPullLayoutParams.setMargins(0, top, 0, 0)
}
private fun setContentViewMarTop(top: Int) {
val mContentViewParams = main_content.layoutParams as RelativeLayout.LayoutParams
mContentViewParams.setMargins(0, top, 0, 0)
}
override fun pullViewShow(state: ScrollState) {
main_root.setPullRelativeLayoutState(state)
main_info.visibility = View.VISIBLE
}
override fun pullViewHide(state: ScrollState) {
main_root.setPullRelativeLayoutState(state)
main_header.visibility = View.INVISIBLE
}
override fun pullViewMove(state: ScrollState, offset: Int) {
main_root.setPullRelativeLayoutState(state)
main_info.visibility = View.INVISIBLE
}
override fun pullViewOpenStart() {
if (main_header.isScrollShow) {
main_root.scrollTo(0, 0)
}
main_header.visibility = View.VISIBLE
main_info.visibility = View.VISIBLE
main_header_mask.visibility = View.INVISIBLE
}
override fun pullViewOpenFinish() {
main_header_mask.visibility = View.VISIBLE
val manager = main_rcv.layoutManager as LinearLayoutManager?
manager!!.scrollToPosition(0)
}
override fun onClick(v: View) {
if (v.id == R.id.main_open_iv) {
openOutUi()
}
}
private fun openOutUi() {
main_body.open()
main_header.scrollShow()
}
override fun onBackPressed() {
if (main_body.state === ScrollState.HIDE) {
openOutUi()
} else {
super.onBackPressed()
android.os.Process.killProcess(android.os.Process.myPid())
}
}
override fun onScrollChange(v: View, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) {
val offset = (mHeaderHeight * 0.7).toInt()
if (scrollY > offset && main_body.state === ScrollState.HIDE) {
main_header.isScrollShow = true
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Above all, you can display any message you want in-app layout by customizing the respective parts. I have used simple texts to display the different sections in android. Hence, you can utilize different gadgets or symbols and you can likewise utilize this code anyplace in your application as indicated by your need.

Future Directions

Finally, the future directions involve displaying multiple pull layouts from different activities and enabling the different pull layouts by pressing a button rather than scrolling.

Learning Strategies and Tools

Zeroing in on the double pull layout is responsible for the both upwards pull and downwards pull in the same screen by scrolling rather than the single pull layout which is commonly used. In conclusion, we learned how your app can use dual drag creation on Android. Tasks now have to use different functions in their proper functions to use dual drag design on android.

Reflective Analysis

Figuring out how to utilize ScrollView managers in android was a great learning experience. It is an astonishing instrument that makes the comprehension ScrollView endpoints simple. A typical mistake that is normal while utilizing pull layout in android is the length of items inside it, so be cautious with that.

The most common use of duplicate drag editing in android mobile apps to facilitate communication with complex Android apps. This method is very useful for highlighting enough to be recognized. So, get a double drag design to do the required function on android.

In conclusion, I spent 72 hours completing the project and the blog. Finally, check everything here GitHub repository.

Link to the previous post: https://blog.ldtalentwork.com/2020/09/16/how-to-check-internet-connection-programatically-on-android-from-a-button-click-in-kotlin

That’s all for this tutorial!

Hire the author: Dennis M