This is my first post on StackOverflow, so please don't kill me for my poor formatting.
I'm trying to make a Work Tracker App, which logs your time of arrival and time of leave in a MySQL database when you press the button in the app.
I want the app to open the correct (is working / is not working) screen when you launch the app, and I kinda managed to make it work with shared preferences, but I figured it would be more reliable if it would request the status from the database.
The table holding the logs looks like this:
user_id | time_of_arrival | time_of_leave
if the user is still in work, there will be a row where he has time_of_arrival, but the time_of_leave field is NULL.
That's what I want to request here:
private fun checkWorking(
sharedPreferences: SharedPreferences,
localContext: Context
) : Boolean {
val userId = sharedPreferences.getString("userId", "").toString()
var isWorking = false
if (userId != "") {
val handler = Handler(Looper.getMainLooper())
handler.post {
val field = arrayOfNulls<String>(1)
field[0] = "user_id"
val data = arrayOfNulls<String>(1)
data[0] = userId
val putData = PutData(
Database().host + Database().databaseName + "checkWorking.php",
"POST",
field,
data
)
if (putData.startPut()) {
if (putData.onComplete()) {
val result = putData.result
if(result == "You are working") {
isWorking = true
}
}
}
}
}
return isWorking
}
here is the php part:
<?php
require "DataBase.php";
$db = new DataBase();
if ($db->dbConnect()) {
if($db->checkWorking("logs", $_POST['user_id'])) {
echo "Success";
} else echo "Failure";
}
?>
and
function checkWorking($table, $userId) {
$userId = $this->prepareData($userId);
$this->sql = "SELECT * FROM " . $table . " WHERE user_id = '" . $userId . "' AND time_of_leave IS NULL";
$result = mysqli_query($this->connect, $this->sql);
if(mysqli_num_rows($result) != 0) {
return true;
}
return false;
}
(The PHP part works correctly, I just wanted to give full insight about my problem)
My problem is that it always returns false, because I read somewhere that the return finishes faster than the handler.post changing the isWorking variable to true.
How can I fix this issue, I legitimately can't figure out anything else I could try.
Thanks in advance!
yes, the return statement is being called before the handler is done since it will be working on a different thread while the return is still on the main thread.
So, you can solve that by using an interface to return the callback whenever it has been received, first you create the interface as follows:
public interface CallbackListener<T> {
void onSuccess(T response);
}
then you have to modify you method to take this interface as a parameter
private fun checkWorking(
sharedPreferences: SharedPreferences,
localContext: Context,
callback: CallbackListener<Boolean>) {
val userId = sharedPreferences.getString("userId", "").toString()
var isWorking = false
if (userId != "") {
CoroutineScope(IO).launch { //running code on background thread
val field = arrayOfNulls<String>(1)
field[0] = "user_id"
val data = arrayOfNulls<String>(1)
data[0] = userId
val putData = PutData(
Database().host + Database().databaseName + "checkWorking.php",
"POST",
field,
data
)
if (putData.startPut()) {
if (putData.onComplete()) {
val result = putData.result
withContext(Main) {//returning to main thread
if (result == "You are working") {
callback.onSuccess(true)
} else
callback.onSuccess(false)
}
}
}
}
I used kotlin Coroutines here instead of handler, but it can be applied to both of them.
then you can call your new method as follows:
checkWorking(
sharedPreferences,
context,
object: CallbackListener<Boolean>{
override fun onSuccess(response: Boolean?) {
//insert your logic here
}
}
)
Related
I am building an android app in MVVM architecture using Firebase. I am trying to do User's password change and whenever i start my code, application freezes or just stops responding. I spent a lot of time to search what is wrong with it and yet no fix. If anyone know why it behave like this I would appreciate your help. My code:
Function called in fragment:
private fun startChangePasswordDialog(){
val dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_change_password, null)
val builder = AlertDialog.Builder(activity).setView(dialogView)
val dialog: AlertDialog = builder.show()
val changePassword = dialogView.findViewById<Button>(R.id.changePasswordBT)
val cancel = dialogView.findViewById<Button>(R.id.changePasswordCancelBT)
val passwordET = dialogView.findViewById<EditText>(R.id.changePasswordET)
changePassword?.setOnClickListener {
val newPassword = passwordET.text.trim().toString()
if (TextUtils.isEmpty(newPassword) || newPassword.length < viewModel.PASSWORD_MIN_VALUE){
Toast.makeText(requireContext(), R.string.password_too_short, Toast.LENGTH_SHORT).show()
}
else{
viewModel.changeUsersPassword(newPassword)
viewModel.needUserAuthentication.observe(requireActivity(), {
if (it == true) reAuthenticateUser()
})
}
dialog.dismiss()
}
cancel?.setOnClickListener {
dialog.dismiss()
}
ViewModel function:
fun changeUsersPassword(password: String) {
Log.d(TAG,"Starting user's password change procedure")
when (repository.changeUserPassword(password)){
PasswordChangeCallbackEnum.FACEBOOK_USER -> {
_toastMessage.value = R.string.facebook_user_password_change
Log.d(TAG, "User's password will not be changed, logged in as Facebook user")
}
PasswordChangeCallbackEnum.PASSWORD_CHANGE_ERROR -> {
_toastMessage.value = R.string.password_change_error
Log.d(TAG, "Error while changing user's password")
}
PasswordChangeCallbackEnum.PASSWORD_CHANGED -> {
_toastMessage.value = R.string.password_change_success
Log.d(TAG, "User's password changed successfully")
}
PasswordChangeCallbackEnum.NEED_USER_AUTHENTICATION -> {
_needUserAuthentication.value = true
}
}
}
Firebase Repository (I have changed it several times when tried to fix this):
fun changeUserPassword(password: String): PasswordChangeCallbackEnum {
var result = PasswordChangeCallbackEnum.PASSWORD_CHANGE_ERROR
if (currentUser != null) {
for (userInfo in currentUser.providerData) {
if (userInfo.providerId == "facebook.com") {
Log.d(TAG, "Cannot change password for user logged in with facebook")
result = PasswordChangeCallbackEnum.FACEBOOK_USER
}
}
}
try{
val updateTask = authentication.currentUser?.updatePassword(password)
updateTask?.addOnSuccessListener {
Log.d(TAG, "User's password change state: SUCCESS")
result = PasswordChangeCallbackEnum.PASSWORD_CHANGED
}
}catch (exception: FirebaseAuthRecentLoginRequiredException){
Log.d(TAG, "Need user to authenticate again")
result = PasswordChangeCallbackEnum.NEED_USER_AUTHENTICATION
}
return result
}
The problem is, you are doing task in ui thread.
Use coroutines for the task to do in worker thread .
you can have more information about coroutines. here
You can also use RxJava for it or some Async task.
It will prevent the ui freezing
I'm trying to see if this "employee" has a service where the serviceComplete field is false (in other words trying to see if this employee has an active service that is not complete) if a Toast message pops up letting the employee know he cannot accept more services has he has an active one. If not the employee should be able to accept the service.
My problem is no matter what I do this firebase query seems to think there are documents in my DB that do not exist. Every time I go to accept the service it displays the toast. Meaning there is a collection "services" where a document has the field "serviceCompleted" which is equal to "false" but in my DB there is no collection "services" under employees
My database showing no collection "services" exist underneath "employees"
and here is my Kotlin code
fun setButton(serviceID: String, eID: String){
val btnAcceptService = view.findViewById<Button>(R.id.btnAcceptService)
btnAcceptService.setOnClickListener {
val queryEmpServices = database.collection("employees").document(eID).collection("services").whereEqualTo("serviceComplete", false)
queryEmpServices.get().addOnSuccessListener { documents ->
if (documents != null){
Toast.makeText(applicationContext,"You already have a service active!", Toast.LENGTH_SHORT).show()
}else {
database.collection("services").document(serviceID).update("saccept", true).addOnSuccessListener {
database.collection("services").document(serviceID).get().addOnSuccessListener { document ->
if (document != null) {
val Location = document.get("ulocation").toString()
val serviceType = document.get("serviceType").toString()
val uComment = document.get("ucomment").toString()
val uID = document.get("uid").toString()
if (document.getBoolean("saccept") == true) {
database.collection("users").document(document.get("uid").toString()).collection("services").document(serviceID).update("saccept", true).addOnSuccessListener {
database.collection("employees").document(mAuth.currentUser!!.uid).get().addOnSuccessListener { document ->
if (document != null) {
val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("dd-MM-yyyy HH:mm:ss")
val acceptDate = simpleDateFormat.format(calendar.time)
val eFullName = document.get("ename").toString() + " " + document.get("esurname").toString()
val eCompany = document.get("ecompany").toString()
database.collection("users").document(uID).get().addOnSuccessListener { document ->
val uName = document.get("name").toString()
val uPhonenumber = document.get("phonenumber").toString()
val serviceAccept = EmployeeServiceAccept(acceptDate, serviceID, Location, serviceType, uComment, uName, uPhonenumber, false)
database.collection("employees").document(mAuth.currentUser!!.uid).collection("acceptedservices").document(serviceID).set(serviceAccept)
database.collection("services").document(serviceID).update("acceptedby", eFullName + ", " + eCompany)
database.collection("users").document(uID).collection("services").document(serviceID).update("acceptedby", eFullName + ", " + eCompany)
Toast.makeText(applicationContext, "Service Accepted", Toast.LENGTH_SHORT).show()
}
}
}
}
}
} else {
Toast.makeText(applicationContext, "Failed to accept service", Toast.LENGTH_SHORT).show()
}
When you are using the Task.addOnSuccessListener(OnSuccessListener) method on a Query object, the result that you get can be a success or a failure, never both and never null. It will always be one, or the other.
That being said, you should never check the documents object against nullity because it can never be null. What you should do instead, is to check the "documents" object, which is of type QuerySnapshot to see if isEmpty() or not:
if (!documents.isEmpty){
Toast.makeText(applicationContext,"You already have a service active!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext,"You don't have a service active!", Toast.LENGTH_SHORT).show()
}
Where indeed the Toast message from the "else" part of the statement will be displayed, as there are no documents present in the QuerySnapshot object:
"You don't have a service active!"
I am rewriting a java class to kotlin replacing callback with a suspend function. This is my java code:
#IgnoreExtraProperties
public class DeviceType {
public String manufacturer;
public String marketName;
public String model;
public DeviceType(String manufacturer, String marketName, String model) {
this.manufacturer = manufacturer;
this.marketName = marketName;
this.model = model;
}
public DeviceType(){}
public DeviceType(Context context) {
DeviceName.with(context).request(new DeviceName.Callback() {
#Override
public void onFinished(DeviceName.DeviceInfo info, Exception error) {
if (error == null) {
manufacturer = info.manufacturer;
marketName = info.marketName;
model = info.model;
} else
Log.e("DeviceType: ", error.getMessage());
}
});
}
#Override
public String toString() {
if (model == null) {
return "No device type recognized!";
} else {
if (marketName.equals(model))
return manufacturer + " " +marketName;
else
return manufacturer + " " +marketName+ " (" +model+ ")";
}
}
DeviceName class belongs to library AndroidDeviceNames.
Below is my new code in Kotlin:
#IgnoreExtraProperties
data class DeviceType(
var manufacturer: String? = null,
var marketName: String? = null,
var model: String? = null
) {
constructor(context: Context) : this(
context.deviceType()?.manufacturer,
context.deviceType()?.marketName,
context.deviceType()?.model
)
override fun toString(): String {
val stringSuffix =
if (marketName == model)
""
else
" ($model)"
return model?.let { "$manufacturer $marketName$stringSuffix" }
?: "No device type recognized!"
}
}
/**
* return DeviceType "from" UI Context
*/
fun Context.deviceType(): DeviceType? = runBlocking {
withContext(Dispatchers.IO) {
/*
delay(1000L)
DeviceType("Nokia","Banana","R2D2")
^
This works!
*/
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
// ^ that doesn't!
}
}
suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? = suspendCoroutine { cont ->
DeviceName.with(context).request { info, error ->
if (error == null) {
cont.resume(DeviceType(
info.manufacturer,
info.marketName,
info.model
))
} else
cont.resumeWithException(Throwable(error.message))
.let {
Log.e(
"FirebaseUserData",
"DeviceName.Request.awaitWith(): $error.message"
)
}
}
}
Executing deviceType().toString()) in MainActivity makes infinite looping in runBlocking() function.
The fundamental question is of course "why my implementation of awaitWith() does not work?", but I am also interested, taking first steps in kotlin and coroutines, if I should provide additional solutions for exception handling, as I read the "coroutines may hide exceptions".
And one more question:
Is Dispatcher.IO here OK? DeviceName gets data from Google API json query.
Should I use that dispatcher type also for coroutines related to firebase DB?
First of all, responding to the question's title, the loop is happening because the constructor is calling Context.deviceType() that calls DeviceName.Request.awaitWith that calls the constructor again:
cont.resume(DeviceType(
info.manufacturer,
info.marketName,
info.model
))
The Context.deviceType() return a DeviceType by itself, but you desire to use it to configure each attribute in the initialization. Each DeviceType's attribute initialization instantiate a DeviceType which each attribute instantiate another DeviceType and so on....
Using Dispatcher.IO is OK and even desired when it comes to IO operations, like network, but you are not quite using it.
The runBlocking call blocks the current thread. The way you are using is like that:
## Assume we are on Thread (A)
fun Context.deviceType(): DeviceType? = runBlocking { ## Still in thread (A)
withContext(Dispatchers.IO) { ## Execute in an IO thread pool, but (A) is waiting
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
} ## Returns to thread (A)
} # Resumes Thread (A)
So, although this is kinda running in an IO dispatcher, the calling thread is blocked until the execution is finished, making it synchronous and indifferent.
Actually my goal was to see the deviceType() function output in non-coroutine environment. This function will be used anyway in other suspend functions or coroutine scope.
This is DeviceType class with its public functions without additional constructor:
#IgnoreExtraProperties
data class DeviceType(
var manufacturer: String? = null,
var marketName: String? = null,
var model: String? = null
) {
override fun toString(): String {
val stringSuffix =
if (marketName == model)
""
else
" ($model)"
return model?.let { "$manufacturer $marketName$stringSuffix" }
?: "No device type recognized!"
}
}
fun Context.deviceTypeByRunBlocking(): DeviceType? = runBlocking {
withContext(Dispatchers.IO) {
DeviceName
.with(this#deviceTypeNoSuspend)
.awaitWith(this#deviceTypeNoSuspend)
}
}
suspend fun Context.deviceType(): DeviceType? =
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
private suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? =
suspendCoroutine { cont ->
DeviceName.with(context).request { info, error ->
if (error == null) {
cont.resume(
DeviceType(
info.manufacturer,
info.marketName,
info.model
)
//.also{Log.d("TAG","Inside awaitWith(): $it")}
)
} else
cont.resumeWithException(Throwable(error.message))
.let {
Log.e(
"TAG",
"DeviceName.Request.awaitWith(): $error.message"
)
}
}
}
Main Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch { Log.d("MainActivity", "${this#MainActivity.deviceType()}") }
//^ this works
Log.d("MainActivity", "${this.deviceTypeByRunBlocking()}")
//^ this still does not, loops in joinBlocking(), isComplete = false
}
}
I know that using GlobalScope is not recommended, but for testing it is fine for me.
I'm loading some info in async this way:
doAsync {
val car = initCar()
onComplete {
Log.e("async", "fired")
loadedCar = car
loadCarView()
}
}
loadedCar variable is a simple CarModel and initCar() function returns already initialized CarModel.
private fun initCar(): Car {
fromVoid = intent.extras == null || intent.extras!!.isEmpty || !intent.hasExtra("car_id")
val car = Car()
if (fromVoid) {
car.name = ""
car.model = ""
dbManager.add(car)
car.id = dbManager.latestInsertId
} else {
car.id = intent.getLongExtra("car_id", -1)
car.name = intent.getStringExtra("car_name")
car.model = intent.getStringExtra("car_model")
}
return car
}
and loadCarView() simply inits textViews and EditTexts...
Problem is that once application is installed and this activity opened, it does not fire doAsync function, but once i re-launch application, it does... I mean i'v tested multiple times and that's usually a case.
P.S also want to note that application was not originally written in kotlin and was converted, if it gives something...
I'm very new to Kotlin\Android so this is probably an obvious mistake but I am stuck and need some help.
What I am doing is getting a user to enter their name, taking that name and check against a mySQL DB (this works fine), and then depending on their authorization level return a 0 or 1, this is the part I'm having issues with.
I have tried using return, creating a global class, adding to a mutable list and setting it but nothing works.
In Main I have this.
var useName:String = editText.text.toString()
verifiedID = verifyUser(useName) //Call the function here
// verifiedID = Global.validLogin //Was used to test Global class
ET_HelloW.setText("$useName ID $verifiedID")
In the verify user class
var ucounter = 0
fun verifyUser(userName: String) :Int {
//getting the record values
val userName = userName
var uname: String = ""
var uauth: String = ""
//creating volley string request
// Checking to see if the Username entered exists in the DBEd
val stringRequest = object : StringRequest(Request.Method.POST, PHPPost.URL_CHECK_LOGIN,
Response.Listener<String> { response ->
try {
val obj = JSONObject(response)
if (!obj.getBoolean("error")){
val array = obj.getJSONArray("credens")
for (i in 0..array.length() - 1) {
val objectArtist = array.getJSONObject(i)
uname = objectUser.getString("name")
uauth = objectUser.getString("auth_level")
}
if (uauth.toInt()<2)
{
Global.validLogin = 0 //This sets
ucounter = 0 //This sets
}else{
Global.validLogin = 1 //This sets when checking code but is not picked up when called in Main
ucounter = 1 //Debugger shows that I do reach here and that the var is set, but resets to 0 at the point of returning.
}
}else{
}
} catch (e: JSONException) {
e.printStackTrace()
}
},
object : Response.ErrorListener {
override fun onErrorResponse(volleyError: VolleyError) {
}
}) {
#Throws(AuthFailureError::class)
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("name", userName)
return params
}
}
//adding request to queue
VolleySingleton.instance?.addToRequestQueue(stringRequest)
return ucounter //always returns zero
// return 1 // If set manually this will be returned.
}
I'm pretty sure that the value is being reset to 0 before it gets to the return at the end but even when I set it as a global it did not work.
Again am sure its a rookie mistake but would appreciate some help.