Home > Enterprise >  Kotlin, receiving data from php api too late. Need to press my button twice before data is received
Kotlin, receiving data from php api too late. Need to press my button twice before data is received

Time:01-03

I need to press the "loginbutton" twice, before I am receiving any data. The first time I press the button, I am receiving data: "null". The second time, I am receiving the necessary data for my login. The question is: "How can I receive my data by the first clickevent?"

XML:

        android:id="@ id/login_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginEnd="30dp"
        android:padding="10dp"
        android:text="@string/login_login"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

LoginActivity:



private lateinit var listIntent: Intent


class LoginActivity : AppCompatActivity() {

    private lateinit var binding: FragmentLoginBinding
    private val viewModel: LoginViewModel by viewModels()
    private val EMAIL_ADDRESS_PATTERN = Pattern.compile(
        "[a-zA-Z0-9\\ \\.\\_\\%\\-\\ ]{1,256}"  
                "\\@"  
                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}"  
                "("  
                "\\."  
                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}"  
                ") "
    )


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        try {
            this.supportActionBar!!.hide()
        } catch (e: NullPointerException) {
        }
        binding = FragmentLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)

        **binding.loginButton.setOnClickListener() { launchMain() }**
        binding.loginTextNewHereRegister.setOnClickListener() { launchRegister() }
        binding.loginTextForgotPassword.setOnClickListener() { launchPasswordForgot() }
    }


    private fun launchMain() {
        if (binding.loginEmailEditText.text?.isEmpty() == true) {
            Toast.makeText(this, "Sorry, please put in a email.", Toast.LENGTH_SHORT).show()
        } else if (binding.loginPasswordEditText.text?.isEmpty() == true) {
            Toast.makeText(this, "Sorry, please put in a password.", Toast.LENGTH_SHORT).show()
        } else if (!isValidString(binding.loginEmailEditText.text.toString())) {
            Toast.makeText(
                this,
                "Sorry, your email does not meet the requirements.",
                Toast.LENGTH_SHORT
            ).show()
        } else {
            viewModel.login(
                binding.loginEmailEditText.text.toString(),
                sha256(binding.loginPasswordEditText.text.toString())
            )
            if (viewModel.doorgaan.value == true) {
                listIntent = Intent(this, MainActivity::class.java)
                startActivity(listIntent)
            } else {
                Toast.makeText(this, viewModel.id.value.toString(), Toast.LENGTH_SHORT).show()
               // Toast.makeText(this, "Sorry, login credentials are wrong.", Toast.LENGTH_SHORT).show()
                binding.loginPasswordEditText.text?.clear()
            }
        }
    }


    private fun isValidString(str: String): Boolean {
        return EMAIL_ADDRESS_PATTERN.matcher(str).matches()
    }

    private fun launchRegister() {
        listIntent = Intent(this, RegisterActivity::class.java)
        startActivity(listIntent)
    }

    private fun launchPasswordForgot() {
        listIntent = Intent(this, ForgotPasswordActivity::class.java)
        startActivity(listIntent)
    }
}

LoginViewModel.kt

class LoginViewModel : ViewModel() {
    private val _status = MutableLiveData<String>()
    private val _id = MutableLiveData<Int>()
    private val _doorgaan = MutableLiveData<Boolean>()

    // The external immutable LiveData for the request status
    val status: MutableLiveData<String> = _status
    val id: MutableLiveData<Int> = _id
    val doorgaan: MutableLiveData<Boolean> = _doorgaan


    /* init {
         setStatus()
     }*/

     fun login(email: String, password: String) {
        _login(email, password)
     }

     private fun _login(email: String, password: String) {
        viewModelScope.launch {
            //val loginResult = gson.fromJson(MyApi.retroFitService.login(email,password),DataAccount::class.java)
            //_status.value = loginResult

            val loginResult = MyApi.retroFitService.login(email, password)
            _status.value = loginResult
            if (_status.value == null){
                _status.value = loginResult
            }
            val jsonObject = JSONTokener(MyApi.retroFitService.login(email, password)).nextValue() as JSONObject
            if (jsonObject.length() > 0) {
                val stringArray = jsonObject.getString("data")
                val jsonArray = JSONArray(stringArray)
                for (i in 0 until jsonArray.length()) {
                    _id.value = jsonArray.getJSONObject(i).getInt("accountid")
                }
                _doorgaan.value = _id.value != null
            }
        }
    }
}

MyApiService.kt

private const val baseUrl = "https://private :)/php/"

private val retrofit = Retrofit.Builder()
    .addConverterFactory(ScalarsConverterFactory.create())
    .baseUrl(baseUrl)
    .build()

interface MyApiService {

    @FormUrlEncoded
    @POST("LOGIN_KOTLIN.php")
    suspend fun login(
        @Field("email") email: String,
        @Field("password") password: String
    ): String

}

object MyApi {
    val retroFitService: MyApiService by lazy {
        retrofit.create(MyApiService::class.java)
    }
}

PHPAPI:

$stmt = $conn->prepare("select * FROM Account where email like ? and password like ?"); 


if (!$stmt){
    //oeps, probleem met prepared statement!
    $response['code'] = 7;
    $response['status'] = 200; // 'ok' status, anders een bad request ...
    $response['data'] = $conn->error;
    deliver_response($response);
}
// bind parameters
// s staat voor string
// i staat voor integer
// d staat voor double
// b staat voor blob
// "ss" staat dus voor String, String
if(!$stmt->bind_param("ss", $_POST['email'], $_POST['password'])){
    // binden van de parameters is mislukt
    $response['code'] = 7;
    $response['status'] = 200; // 'ok' status, anders een bad request ...
    $response['data'] = $conn->error;
    deliver_response($response);
}

if (!$stmt->execute()) {
    // het uitvoeren van het statement zelf mislukte
    $response['code'] = 7;
    $response['status'] = $api_response_code[$response['code']]['HTTP Response'];
    $response['data'] = $conn->error;
    deliver_response($response);
}

$result = $stmt->get_result();

if (!$result) {
    // er kon geen resultset worden opgehaald
    $response['code'] = 7;
    $response['status'] = $api_response_code[$response['code']]['HTTP Response'];
    $response['data'] = $conn->error;
    deliver_response($response);
}

// Vorm de resultset om naar een structuur die we makkelijk kunnen 
// doorgeven en stop deze in $response['data']

$response['data'] = getJsonObjFromResult($result); // -> fetch_all(MYSQLI_ASSOC)

// maak geheugen vrij op de server door de resultset te verwijderen
$result->free();
// sluit de connectie met de databank
$conn->close();
// Return Response to browser
deliver_response($response);

CodePudding user response:

Your login method is asnychronous, the method will return immediately even if the coroutine is still running (giving null data since it hasn't had time to complete).

Method 1 - Callback

One way of handling this would be to pass in a callback like this

fun login(email: String, password: String, onComplete: ()->Unit) {
    viewModelScope.launch {
        //do slow/network/suspending stuff
        onComplete()
    }
}

then call it like this

viewModel.login(
    binding.loginEmailEditText.text.toString(),
    sha256(binding.loginPasswordEditText.text.toString())
) {
    // the code in here is your "onComplete" callback, and won't run
    // until the login task is done
    if (viewModel.doorgaan.value == true) {
        listIntent = Intent(this, MainActivity::class.java)
        startActivity(listIntent)
    } else {
        Toast.makeText(this, viewModel.id.value.toString(), Toast.LENGTH_SHORT).show()
        binding.loginPasswordEditText.text?.clear()
    }
}

Method 2 - Observe the LiveData (better)

Alternately, you could have your activity observe the doorgaan LiveData and respond when it changes, which would also cause the program to wait appropriately until it had data there (just make sure you always set a value to the data inside _login, the current code does not set a value in some cases).

// add this inside onCreate, and remove these calls from launchMain
viewModel.doorgaan.observe(this) { value ->
    if (value == true) {
        listIntent = Intent(this, MainActivity::class.java)
        startActivity(listIntent)
    } else {
        Toast.makeText(this, viewModel.id.value.toString(), Toast.LENGTH_SHORT).show()
        binding.loginPasswordEditText.text?.clear()
    }
}

To make sure something always gets posted, you could do something like this inside _login

var auth = false
if (jsonObject.length() > 0) {
    val stringArray = jsonObject.getString("data")
    val jsonArray = JSONArray(stringArray)
    for (i in 0 until jsonArray.length()) {
        _id.value = jsonArray.getJSONObject(i).getInt("accountid")
    }
    auth = _id.value != null
}
_doorgaan.postValue(auth)
  • Related