Issue
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);
Solution
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)
Answered By - Tyler V
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.