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...
Related
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
}
}
)
I am making a request with coroutines based on a user name, which returns a list of Object<Profile>, and with that list I am making another request with each object, and then switching and passing the info to another screen, but such process is making the app super slow and I would like to find a better way or a way to not making this process so slow. Here my code
Fragment from where I am starting the process and where the app is getting super slow
emptyHomeViewModel.getPlayersListByName(text)
emptyHomeViewModel.listOfPlayersByNameLiveData.observe(viewLifecycleOwner) { playersByName ->
emptyHomeViewModel.getPlayersProfileByName(playersByName)
emptyHomeViewModel.listOfProfilesByID.observe(viewLifecycleOwner) { profiles ->
if (profiles != null) {
val list: Array<Profile> = profiles.toTypedArray()
bundle = Bundle().apply {
putSerializable("user", list)
}
findNavController().navigate(
R.id.action_emptyHomeFragment_to_selectUserFragment,
bundle
)
}
}
}
ViewModel from where I am executing the coroutines and making the request to the API
fun getPlayersListByName(playerName: String) = viewModelScope.launch {
val playersList = getPlayersByPersonaNameUseCase.getPlayersByName(playerName)
if (playersList != null) {
_listOfPlayersByNameLiveData.postValue(playersList)
}
}
fun getPlayersProfileByName(playersByName: List<PlayerByPersonaNameItem>?) =
viewModelScope.launch {
var playersProfileList: ArrayList<Profile> = arrayListOf()
if (playersByName != null) {
for (player in playersByName) {
getPlayerByIDUseCase.getPlayerById(player.accountId)
?.let { playersProfileList.add(it) }
}
_listOfProfilesByID.postValue(playersProfileList)
}
}
You can actually load profiles in parallel, preventing loading them one after another, to decrease time of loading data:
fun getPlayersProfileByName(playersByName: List<PlayerByPersonaNameItem>?) =
viewModelScope.launch {
val playersProfileList: List<Profile> = playersByName?.map { player ->
async {
getPlayerByIDUseCase.getPlayerById(player.accountId)
}
}.awaitAll().filterNotNull()
_listOfProfilesByID.postValue(playersProfileList)
}
Also you can improve it a little bit by removing additional LiveData observer and calling getPlayersProfileByName right after you get playersList:
fun getPlayersListByName(playerName: String) = viewModelScope.launch {
val playersList = getPlayersByPersonaNameUseCase.getPlayersByName(playerName)
getPlayersProfileByName(playersList)
}
In below code my observer gets called multiple time after storing all users from arguments to result arraylist. I am new to observe pattern so I am not sure what I am doing wrong here.
private lateinit var usersObserver: Observer<List<User?>?>
override fun onCreate(savedInstanceState: Bundle?) {
usersObservar = Observer {
userResults = populateResults(it)
}
}
private fun populateResults(users: List<User?>): MutableList<UserModel> {
val results: MutableList<UserModel> = ArrayList()
for (user in users) {
//Ignore potential null predictions
if ((user != null) &&user.isUserNotNull()) {
user.id?.let {
searchResultsViewModel.getUserById(it).observe(
this,
Observer { ud ->
if (ud != null && ud.hasNonNullLatLngOffsetMembers()) {
results.add(
UserModel(
name = user.placeId!!,
address = ud.address
displayed = false
)
)
}
}
)
}
}
}
return results
}
I assume you are calling popoulateResults() multiple times. When you call searchResultsViewModel.getUserById(it).observe() you pass it a new instance of the Observer therefore everytime the observer is called the code inside the observer is getting executed. An easy fix should be defining the observer as a property outside the function like this
val observer = Observer { your code }
and use it like
searchResultsViewModel.getUserById(it).observe(this, observer)
In my view, I need to wait for two different data sets to populate a recycler view.
This is my completely non working function in ViewModel, here it shows what I'm trying to achieve.
val resultWrapperLiveData = MutableLiveData<SearchResultWrapper>()
fun searchAandB(query: String) {
var a: AObject? = null
var b: BObject? = null
CoroutineScope(Dispatchers.Main).launch {
launch {
a = repo.searchA(query = query)
}
launch {
b = repo.searchB(query = query)
}
resultWrapperLiveData.postValue(SearchResultWrapper(a, b))
}
}
Thanks in advance!
I think this is a solution. The only problem with this is it waits to start the second api call. It would be better if I can get both of these running at the same time.
fun searchAandB(query: String) {
CoroutineScope(Dispatchers.Main).launch {
val a = CoroutineScope(Dispatchers.IO).async rt#{
return#rt repo.searchA(query = query)
}.await()
val b = CoroutineScope(Dispatchers.IO).async rt#{
return#rt repo.searchB(query = query)
}.await()
_searchResultWrapper.postValue(SearchResultWrapper(a, b))
}
}
I'm using nested Coroutine blocks in my code. And I'm getting a null value when I tried to get Deferred type's result to a variable. Thus, It causes a casting problem which is kotlin.TypeCastException: null cannot be cast to non-null type kotlin.collections.ArrayList in getNearbyHealthInstitutions() method's return line. I believe, I did the right implementation at some point but what am I missing to get null value from Deferred's result? The funny thing is when I debug it, it does return the expected value. I think it should be the concurrency problem or I don't have any idea why it works in debug mode in the first place. Any ideas fellas?
// Invocation point where resides in a callback
GlobalScope.launch(Dispatchers.Main) {
nearbyHealthInstitutionSites.value = getNearbyHealthInstitutions()
}
private suspend fun getNearbyHealthInstitutions(radius: Meter = DEFAULT_KM_RADIUS) : ArrayList<Hospital> {
return CoroutineScope(Dispatchers.IO).async {
val list = getHealthInstitutions()
val filteredList = list?.filter { it.city == state?.toUpperCase() } as MutableList<Hospital>
Log.i(MTAG, "nearby list is $filteredList")
Log.i(MTAG, "nearby list's size is ${filteredList.size}")
var deferred: Deferred<MutableList<Hospital>>? = null
addAllNearbyLocations(onEnd = { nearbyHealthInstitutions ->
deferred = async {
findNearbyOfficialHealthInstitutions(
officialHealthInstitutionList = filteredList as ArrayList<Hospital>,
nearbyHealthInstitutions = nearbyHealthInstitutions
)
}
})
val result = deferred?.await()
return#async result as ArrayList<Hospital>
}.await()
}
private suspend fun findNearbyOfficialHealthInstitutions(officialHealthInstitutionList: ArrayList<Hospital>, nearbyHealthInstitutions: MutableList<Hospital>): MutableList<Hospital> {
return GlobalScope.async(Dispatchers.Default) {
val result = mutableListOf<Hospital>()
officialHealthInstitutionList.forEach {
nearbyHealthInstitutions.forEach { hospital ->
StringSimilarity.printSimilarity(it.name, hospital.name)
val similarity = StringSimilarity.similarity(it.name, hospital.name.toUpperCase())
if (similarity > SIMILARITY_THRESHOLD) {
Log.i(MTAG, "findNearbyOfficialHealthInstitutions() - ${it.name} and ${hospital.name.toUpperCase()} have %$similarity")
result.add(hospital)
}
}
}
Log.i(TAG, "------------------------------------------")
result.forEach {
Log.i(MTAG, "findNearbyOfficialHealthInstitutions() - hospital.name is ${it.name}")
}
return#async result
}.await()
}
Since addAllNearbyLocations() is asynchonous, your coroutine needs to wait for the callback to be called to continue its execution. You can use suspendCoroutine API for this.
val result = suspendCoroutine { continuation ->
addAllNearbyLocations(onEnd = { nearbyHealthInstitutions ->
findNearbyOfficialHealthInstitutions(
officialHealthInstitutionList = filteredList as ArrayList<Hospital>,
nearbyHealthInstitutions = nearbyHealthInstitutions
).let { found -> continuation.resume(found) }
})
}
On a separate note you should use List instead of ArrayList or MutableList, you should always look to use a generic interface instead of a specific implementation of that interface. This also gets rids of some of the castings (ideally you should have no castings in this code).