Android (Kotlin) Login and Register example with Retrofit
Output :
Create Table :
PHP Files :
config.php
<?php
$host="localhost";
$user="root";
$password="";
$db = "kotlin_example";
$con = mysqli_connect($host,$user,$password,$db);
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}else{ //echo "Connect";
}
?>
register.php
<?php
if($_SERVER['REQUEST_METHOD']=='POST'){
include_once("config.php");
$postdata = file_get_contents("php://input");
if (isset($postdata)) {
$request1 = json_decode($postdata);
$email = $request1->email;
$username = $request1->username;
$password = $request1->password;
if($email == '' || $username == '' || $password == ''){
echo json_encode(array( "status" => "false","message" => "Parameter missing!") );
}else{
$query= "SELECT * FROM user WHERE username='$username'";
$result= mysqli_query($con, $query);
if(mysqli_num_rows($result) > 0){
echo json_encode(array( "status" => "false","message" => "Username already exist!") );
}else{
$query = "INSERT INTO user (email,username,password) VALUES ('$email','$username','$password')";
if(mysqli_query($con,$query)){
$query= "SELECT * FROM user WHERE username='$username'";
$result= mysqli_query($con, $query);
$emparray = array();
if(mysqli_num_rows($result) > 0){
while ($row = mysqli_fetch_assoc($result)) {
$emparray[] = $row;
echo json_encode(array( "status" => "true","message" => "Successfully registered!" , "data" => $row) );
}
}
}else{
echo json_encode(array( "status" => "false","message" => "Error occured, please try again!") );
}
}
mysqli_close($con);
}
}
} else{
echo json_encode(array( "status" => "false","message" => "Error occured, please try again!") );
}
?>
login.php
<?php
if($_SERVER['REQUEST_METHOD']=='POST'){
include_once("config.php");
$postdata = file_get_contents("php://input");
if (isset($postdata)) {
$request1 = json_decode($postdata);
$email = $request1->email;
$password = $request1->password;
if( $email == '' || $password == '' ){
echo json_encode(array( "status" => false,"message" => "Parameter missing!") );
}else{
$query= "SELECT * FROM user WHERE email='$email' AND password='$password'";
$result= mysqli_query($con, $query);
if(mysqli_num_rows($result) > 0){
$query= "SELECT * FROM user WHERE email='$email' AND password='$password'";
$result= mysqli_query($con, $query);
$emparray;
if(mysqli_num_rows($result) > 0){
while ($row = mysqli_fetch_assoc($result)) {
$emparray = $row;
}
}
echo json_encode(array( "status" => true,"message" => "Login successfully!", "data" => $emparray) );
}else{
echo json_encode(array( "status" => false,"message" => "Invalid email or password!") );
}
mysqli_close($con);
}
}
} else{
echo json_encode(array( "status" => false,"message" => "Error occured, please try again!") );
}
?>
get_user_detail.php
<?php
$response = array();
include_once("config.php");
if (isset($_GET["id"])) {
$id = $_GET['id'];
$query= "SELECT * FROM user WHERE id='$id'";
$result= mysqli_query($con, $query);
$emparray;
if(mysqli_num_rows($result) > 0){
while ($row = mysqli_fetch_assoc($result)) {
$emparray = $row;
echo json_encode(array( "status" => true, "data" => $emparray) );
}
}
else{
echo json_encode(array( "status" => false,"message" => "No User Found!") );
}
}else {
echo json_encode(array( "status" => false,"message" => "Required field(s) is missing!") )
}
?>
build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0' implementation 'com.google.android.material:material:1.0.0'
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hpos.loginregisterusingretrofit">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning"
tools:targetApi="m">
<activity android:name=".HomeActivity"></activity>
<activity android:name=".RegisterActivity" />
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Models
SigninRequest.kt
package com.hpos.loginregisterusingretrofit.models
import com.google.gson.annotations.SerializedName
class SigninRequest(@SerializedName("email") var email: String,
@SerializedName("password") var password: String)
SigninResponse.kt
package com.hpos.loginregisterusingretrofit.models
class SigninResponse(val status: Boolean, val message:String, val data: User)
SignupRequest.kt
package com.hpos.loginregisterusingretrofit.models
import com.google.gson.annotations.SerializedName
class SignupRequest(@SerializedName("email") var email: String,
@SerializedName("username") var username: String,
@SerializedName("password") var password: String)
SignupResponse.kt
package com.hpos.loginregisterusingretrofit.models
class SignupResponse(val status: Boolean, val message:String, val data: User)
UserResponse.kt
package com.hpos.loginregisterusingretrofit.models
data class UserResponse(val status: Boolean, val data: User)
User.kt
package com.hpos.loginregisterusingretrofit.models;
data class User(val id:Int, val username:String, val email:String, val password:String)
Retrofit Files
ApiList
package com.hpos.loginregisterusingretrofit.api
import com.hpos.loginregisterusingretrofit.models.*
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
interface ApiList {
//TODO : Register User
@POST("register.php")
fun doRegister(
@Body signupRequest: SignupRequest
): Call<SignupResponse> // body data
//TODO : Login User
@POST("login.php")
fun doLogin(
@Body signinRequest: SigninRequest
): Call<SigninResponse> // body data
//TODO : Get User
@GET("get_user_detail.php")
fun getUser(@Query("id") id: String): Call<UserResponse>
}
ApiService
package com.hpos.loginregisterusingretrofit.api
import retrofit2.Retrofit
object ApiService {
private val TAG = "--ApiService"
private const val BASE_URL = "http://192.168.1.111/KotlinExample/LoginRegistration/"
fun loginApiCall() = Retrofit.Builder()
.baseUrl(BASE_URL)
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ApiWorker.gsonConverter)
.client(ApiWorker.client)
.build()
.create(ApiList::class.java)!!
}
ApiWorker
package com.hpos.loginregisterusingretrofit.api
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.converter.gson.GsonConverterFactory
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.util.concurrent.TimeUnit
object ApiWorker {
private var mClient: OkHttpClient? = null
private var mGsonConverter: GsonConverterFactory? = null
/**
* Don't forget to remove Interceptors (or change Logging Level to NONE)
* in production! Otherwise people will be able to see your request and response on Log Cat.
*/
val client: OkHttpClient
@Throws(NoSuchAlgorithmException::class, KeyManagementException::class)
get() {
if (mClient == null) {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val httpBuilder = OkHttpClient.Builder()
httpBuilder
.connectTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(interceptor) /// show all JSON in logCat
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
// .addHeader("Authorization", AUTH)
.addHeader("Content-Type", "application/json")
// .method(original.method(), original.body())
val request = requestBuilder.build()
chain.proceed(request)
}
mClient = httpBuilder.build()
}
return mClient!!
}
val gsonConverter: GsonConverterFactory
get() {
if (mGsonConverter == null) {
mGsonConverter = GsonConverterFactory
.create(
GsonBuilder()
.setLenient()
.disableHtmlEscaping()
.create()
)
}
return mGsonConverter!!
}
}
Screens Files
HomeActivity.kt
package com.hpos.loginregisterusingretrofit
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import com.hpos.loginregisterusingretrofit.api.ApiService
import com.hpos.loginregisterusingretrofit.models.User
import com.hpos.loginregisterusingretrofit.models.UserResponse
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class HomeActivity : AppCompatActivity() {
var userId: String = "";
private lateinit var txt_name: TextView
private lateinit var txt_email: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
setTitle("Home")
val intent = getIntent()
userId = intent.getIntExtra("id", 0).toString()
txt_name = findViewById(R.id.txt_name) as TextView
txt_email = findViewById(R.id.txt_email) as TextView
getUser()
}
private fun getUser() {
ApiService.loginApiCall().getUser(userId).enqueue(object : Callback<UserResponse> {
override fun onResponse(
call: Call<UserResponse>,
response: Response<UserResponse>) {
Log.d("Response User ::::", response.body().toString())
if (response.body()!!.status){
txt_name.setText(response.body()!!.data.username)
txt_email.setText(response.body()!!.data.email)
}
}
override fun onFailure(call: Call<UserResponse>, t: Throwable) {
// Log.d("error::::",t?.message)
}
})
}
}
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/my"
android:layout_gravity="center_horizontal"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Name : "
android:textSize="20sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jainish Prajapati"
android:textSize="20sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email : "
android:textSize="20sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/txt_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="jainishprajapati17@gmail.com"
android:textSize="20sp"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
LoginActivity.kt
package com.hpos.loginregisterusingretrofit
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.widget.AppCompatTextView
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.hpos.loginregisterusingretrofit.api.ApiService
import com.hpos.loginregisterusingretrofit.models.SigninRequest
import com.hpos.loginregisterusingretrofit.models.SigninResponse
import org.json.JSONObject
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginActivity : Activity(), View.OnClickListener {
private lateinit var ed_email : TextInputEditText
private lateinit var ed_password : TextInputEditText
private lateinit var btn_signin : MaterialButton
private lateinit var txt_sign_up : AppCompatTextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
ed_email = findViewById(R.id.ed_email) as TextInputEditText
ed_password = findViewById(R.id.ed_password) as TextInputEditText
btn_signin = findViewById(R.id.btn_signin) as MaterialButton
txt_sign_up = findViewById(R.id.txt_sign_up) as AppCompatTextView
btn_signin.setOnClickListener(this)
txt_sign_up.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.txt_sign_up -> {
startActivity(Intent(this@LoginActivity, RegisterActivity::class.java))
}
R.id.btn_signin -> {
if (validation()) {
val json = JSONObject()
json.put("email", ed_email.text.toString())
json.put("password", ed_password.text.toString())
ApiService.loginApiCall().doLogin(
SigninRequest(
ed_email.text.toString(),ed_password.text.toString()
)
).enqueue(object : Callback<SigninResponse> {
override fun onResponse(
call: Call<SigninResponse>,
response: Response<SigninResponse>) {
Log.d("Response::::", response.body().toString())
if (response.body()!!.status){
finish()
val intent = Intent(this@LoginActivity, HomeActivity::class.java)
intent.putExtra("id",response.body()!!.data.id)
startActivity(intent)
}else{
Toast.makeText(applicationContext, response.body()!!.message, Toast.LENGTH_LONG).show()
}
}
override fun onFailure(call: Call<SigninResponse>, t: Throwable) {
}
})
}
}
}
}
fun validation(): Boolean {
var value = true
val password = ed_password.text.toString().trim()
val name = ed_email.text.toString().trim()
if (password.isEmpty()) {
ed_password.error = "Password required"
ed_password.requestFocus()
value = false
}
if (name.isEmpty()) {
ed_email.error = "Email required"
ed_email.requestFocus()
value = false
}
return value;
}
}
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="20dp"
android:layout_centerInParent="true">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/signin"
android:textColor="@android:color/black"
android:textStyle="bold"
android:textSize="36sp"/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/welcome_back"
android:textSize="20sp"
android:textColor="@android:color/darker_gray"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_marginTop="30dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_signin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/sign_in"
android:padding="15dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:layout_marginTop="20dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/don_t_have_an_account"
android:textSize="20sp"
android:textColor="@android:color/darker_gray"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_sign_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sign_up_here"
android:textSize="20sp"
android:textColor="@android:color/black"
android:layout_marginStart="20dp"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
RegisterActivity.kt
package com.hpos.loginregisterusingretrofit
import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.hpos.loginregisterusingretrofit.api.ApiService
import com.hpos.loginregisterusingretrofit.models.SignupRequest
import com.hpos.loginregisterusingretrofit.models.SignupResponse
import org.json.JSONObject
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class RegisterActivity : Activity(), View.OnClickListener {
private lateinit var ed_username: TextInputEditText
private lateinit var ed_email: TextInputEditText
private lateinit var ed_password: TextInputEditText
private lateinit var btn_signup: MaterialButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
ed_username = findViewById(R.id.ed_username) as TextInputEditText
ed_email = findViewById(R.id.ed_email) as TextInputEditText
ed_password = findViewById(R.id.ed_password) as TextInputEditText
btn_signup = findViewById(R.id.btn_signup) as MaterialButton
btn_signup.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.btn_signup -> {
if (validation()) {
val json = JSONObject()
json.put("email", ed_email.text.toString())
json.put("username", ed_username.text.toString())
json.put("password", ed_password.text.toString())
ApiService.loginApiCall().doRegister(
SignupRequest(
ed_email.text.toString(),
ed_username.text.toString(), ed_password.text.toString()
)
).enqueue(object : Callback<SignupResponse> {
override fun onResponse(
call: Call<SignupResponse>,
response: Response<SignupResponse>
) {
Log.d("Response::::", response.body().toString())
val loginResponse : SignupResponse
loginResponse = response.body()!!
if (loginResponse.status){
finish()
}else{
Toast.makeText(applicationContext, response.body()!!.message, Toast.LENGTH_LONG).show()
}
}
override fun onFailure(call: Call<SignupResponse>, t: Throwable) {
}
})
}
}
}
}
fun validation(): Boolean {
var value = true
val email = ed_email.text.toString().trim()
val password = ed_password.text.toString().trim()
val name = ed_username.text.toString().trim()
if (email.isEmpty()) {
ed_email.error = "Email required"
ed_email.requestFocus()
value = false
}
if (password.isEmpty()) {
ed_password.error = "Password required"
ed_password.requestFocus()
value = false
}
if (name.isEmpty()) {
ed_username.error = "Name required"
ed_username.requestFocus()
value = false
}
return value;
}
}
activity_register.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="20dp"
android:layout_centerInParent="true">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/signup"
android:textColor="@android:color/black"
android:textStyle="bold"
android:textSize="36sp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/user_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_marginTop="30dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_signup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/sign_up"
android:padding="15dp"/>
</LinearLayout>
</RelativeLayout>
style.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
string.xml
<resources>
<string name="app_name">LoginRegisterUsingRetrofit</string>
<string name="user_name">User Name</string>
<string name="password">Password</string>
<string name="sign_in">Sign In</string>
<string name="signin">Signin</string>
<string name="welcome_back">Welcome back</string>
<string name="sign_up">Sign Up</string>
<string name="email">Email</string>
<string name="signup">Signup</string>
<string name="don_t_have_an_account">Don\'t have an account?</string>
<string name="sign_up_here">Sign up here</string>
</resources>
color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#5edb92</color>
<color name="colorPrimaryDark">#5edb92</color>
<color name="colorAccent">#5edb92</color>
<color name="colorPrimaryBackground">#0886c7</color>
<color name="colorFormBackground">#e4e4e4</color>
</resources>
Good afternoon, there is no way to download the source code, to be able to better detail your code, thank you very much.
ReplyDeletedata not entry
ReplyDeletecan you explain the code?
ReplyDeleteits so hard to read
ReplyDeletehello, i follow that instruction, but i get error in script .enqueue(object : Callback
ReplyDelete