I am new to the coroutine concept. I'm not very confident in the code I've written. Basically I have a queue of user events that I send to my server. Adding an event is done through the main thread and sends it into a coroutine. If the event was successfully sent, I delete it from the queue, if the send failed, it is not deleted and will be retried for the next cycle. To solve the concurrency issues I used a mutex. Can you tell me if pretty good or horrible and a solution in this case?
My code:
data class GeoDataEvent(
val location : Location,
val category : Int
)
// This class is instantiated in my android service
class GeoDataManager(private var mainRepository: MainRepository) {
private var eventQueue : Queue<GeoDataEvent> = LinkedList()
private val eventQueueMutex : Mutex = Mutex()
fun enqueueEvent(location: Location, category : Int){
CoroutineScope(Dispatchers.IO).launch {
eventQueueMutex.withLock {
eventQueue.add(
GeoDataEvent(
location = location,
category = category
)
)
}
}
}
// Called at each new location by Android location service
private fun processNewLocation(location: Location){
/* Some code */
handleEventQueue()
}
private fun handleEventQueue(){
CoroutineScope(Dispatchers.IO).launch {
eventQueueMutex.withLock {
if (eventQueue.isNotEmpty()) {
mainRepository.getAuthToken()?.let { token ->
eventQueue.peek()?.let { event ->
if (sendEvent(token, event)){
eventQueue.remove()
}
}
}
}
}
}
}
private suspend fun sendEvent(token : String, event : GeoDataEvent) : Boolean {
mainRepository.sendGeoDataEvent(token, event).let { res ->
return res.isSuccessful
}
}
}
Thank you for your help
Related
So I am starting to build a chat app and now I am at the registration screen.
Every time I press the login button,the request is sent only 1 time,like it should do.
The problem starts when I get in return the error message(e.g "Your password is incorrect"),after I get the error,I am pressing the login button again with the same wrong password,and I get Log error that I made but its showing 3 times, at the same time and firebase tells me that I have made too many attempts....
This is what I have done:
ViewModel:
private val _authState by lazy { MutableLiveData<AuthState>(AuthState.Loading) }
val authState: LiveData<AuthState> = _authState
fun loginUser(emailAddress: String, password: String) {
if (!isEmailAddressValid(emailAddress)) {
_authState.value = AuthState.AuthError("Invalid email")
return
} else if (password.isEmpty()) {
_authState.value = AuthState.AuthError("Password field can't be empty")
return
} else if (emailAddress.isEmpty()) {
_authState.value = AuthState.AuthError("Email field can't be empty")
return
}
auth.signInWithEmailAndPassword(emailAddress, password).addOnCompleteListener { task ->
if (task.isSuccessful) {
_authState.value = AuthState.Success
} else {
task.exception?.let {
_authState.value = AuthState.AuthError(it.localizedMessage)
}
}
}
}
This is the Activity:
binding.loginButton.setOnClickListener {
val emailEditText = binding.emailAddressEditText.text.toString()
val passwordEditText = binding.passwordEditText.text.toString()
registerLoginViewModel.loginUser(emailEditText, passwordEditText)
registerLoginViewModel.authState.observe(this#LoginRegisterActivity, object : Observer<AuthState?> {
override fun onChanged(loginState: AuthState?) {
when (loginState) {
is AuthState.Success -> {
hideLoadingScreen()
Toast.makeText(this#LoginRegisterActivity,"Welcome Back!",Toast.LENGTH_SHORT).show()
Intent(this#LoginRegisterActivity, MainActivity::class.java)
finish()
}
is AuthState.AuthError -> {
hideLoadingScreen()
Log.e("Error:","Error Message: ${loginState.message}") // This line returns 3 times after the second attempt
Toast.makeText(this#LoginRegisterActivity,loginState.message,Toast.LENGTH_SHORT).show()
}
else -> {
showLoadingScreen()
}
}
}
})
}
Thank you !
LiveData.observe(...) doesn't need to be in any kind of listener. You can observe in onCreate() of Activity ahead of API call. As it is in your code now, you're adding one new observer every time your click listener is called.
Here's a small example:
class FruitsActivity : AppCompatActivity {
private val binding by lazy {
FruitsActivityBinding.inflate(layoutInflater)
}
private val fruitsViewModel by viewModels<FruitsViewModel>()
#Override
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// Observe from fruitsViewModel.fruits
fruitsViewModel.fruits.observe(this) { fruitList ->
// Use `fruitList` in your adapter
}
// Fetch fruits on tap of a button
binding.loadFruitsButton.setOnClickListener {
fruitsViewModel.fetchFruits()
}
}
}
class FruitsViewModel : ViewModel() {
private val _fruits = MutableLiveData<List<Fruit>>()
val fruits: LiveData<List<Fruit>> = _fruits
fun fetchFruits() {
viewModelScope.launch {
// `someRepository` can be anything that calls an API
// or queries a database to get the required data.
// Repository Pattern + Coroutines recommended
val fruitList = someRepository.fetchFruits()
// if needed, perform any filters or modifications to `fruitList` here
// set the result data on LiveData
_fruits.value = fruitList
}
}
}
So, this is what happens:
Activity launches.
Initializes binding and fruitsViewModel.
Adds an Observer on fruits from fruitsViewModel
Sets click listener on a button to load fruits
When you tap the button, fruitsViewModel fetches fruits and sets result data on LiveData (_fruits).
LiveData finds its observers and notifies them about new data.
Let me know if you have any questions or if there's something wrong. I wrote the code directly in this text-field, so there might be a dot, comma or colon misplaced or missing.
PROBLEM STATEMENT
: When i press register button for register new user it show register success response in toast from live data, but when i tried to do same button trigger it show again register success response message from API & then also show phone number exist response from API in toast. It means old response return by live data too. So how can i solve this recursive live data response return issue?
HERE is the problem video link to understand issue
Check here https://drive.google.com/file/d/1-hKGQh9k0EIYJcbInwjD5dB33LXV5GEn/view?usp=sharing
NEED ARGENT HELP
My Api Interface
interface ApiServices {
/*
* USER LOGIN (GENERAL USER)
* */
#POST("authentication.php")
suspend fun loginUser(#Body requestBody: RequestBody): Response<BaseResponse>
}
My Repository Class
class AuthenticationRepository {
var apiServices: ApiServices = ApiClient.client!!.create(ApiServices::class.java)
suspend fun UserLogin(requestBody: RequestBody) = apiServices.loginUser(requestBody)
}
My View Model Class
class RegistrationViewModel : BaseViewModel() {
val respository: AuthenticationRepository = AuthenticationRepository()
private val _registerResponse = MutableLiveData<BaseResponse>()
val registerResponse: LiveData<BaseResponse> get() = _registerResponse
/*
* USER REGISTRATION [GENERAL USER]
* */
internal fun performUserLogin(requestBody: RequestBody, onSuccess: () -> Unit) {
ioScope.launch {
isLoading.postValue(true)
tryCatch({
val response = respository.UserLogin(requestBody)
if (response.isSuccessful) {
mainScope.launch {
onSuccess.invoke()
isLoading.postValue(false)
_registerResponse.postValue(response.body())
}
} else {
isLoading.postValue(false)
}
}, {
isLoading.postValue(false)
hasError.postValue(it)
})
}
}
}
My Registration Activity
class RegistrationActivity : BaseActivity<ActivityRegistrationBinding>() {
override val layoutRes: Int
get() = R.layout.activity_registration
private val viewModel: RegistrationViewModel by viewModels()
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
//return old reponse here then also new reponse multiple time
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
override fun toolbarController() {
binding.backactiontoolbar.menutitletoolbar.text = "Registration"
binding.backactiontoolbar.menuicontoolbar.setOnClickListener { onBackPressed() }
}
override fun processIntentData(data: Uri) {}
}
your registerResponse live data observe inside button click listener, so that's why it's observing two times! your registerResponse live data should observe data out side of button Click listener -
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
}
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
LiveData is a state holder, it's not really meant to be used as an event stream. There is a number of articles however about the topic like this one which describe the possible solutions, including SingleLiveEvent implementation taken from google samples.
But as of now kotlin coroutines library provides better solutions. In particular, channels are very useful for event streams, because they implement fan-out behaviour, so you can have multiple event consumers, but each event will be handled only once. Channel.receiveAsFlow can be very convenient to expose the stream as flow. Otherwise, SharedFlow is a good candidate for event bus implementation. Just be careful with replay and extraBufferCapacity parameters.
In my app, I have this flow:
ClickListender in my fragment:
search_button.setOnClickListener {
if(search_input.text.isNullOrEmpty())
Toast.makeText(activity, "Input Error", Toast.LENGTH_LONG).show()
else
viewModel.onSearchButtonClicked(search_input.text.toString())
}
onSearchButtonClicked inside viewModel:
fun onSearchButtonClicked(input: String) {
coroutineScope.launch {
repo.insertToDatabase(input)
}
}
insertToDatabase inside Repository:
suspend fun insertToDatabase(string: String) {
withContext(Dispatchers.IO) {
val dataList =
ExternalApi.retrofitCall.getData(string).await()
if (dataList.intialDataResult < 1) {
//show error
} else {
//all good
database.myDataBase.insertAll(dataList)
}
}
}
I need to show error message if intialDataResult is less then one.
I thought about create MutableLiveData inside my repository with initial value of false and listen from the fragment through the viewModel, but it's not good approach because I have no way to set the LiveData to "false" again after I show error message.
I also tried to return bool from the insertToDatabase function and decide if to show error or not, with no success.
Any ideas how can I solve this?
Why not create a LiveData to manage your work's result state?
Create a class to store result of work why sealed class?
sealed class ResultState{
object Success: ResultState() // this is object because I added no params
data class Failure(val message: String): ResultState()
}
Create a LiveData to report this result
val stateLiveData = MutableLiveData<ResultState>()
Make insertToDatabase() return a result
suspend fun insertToDatabase(input: String): ResultState {
return withContext<ResultState>(Dispatchers.IO) {
val dataList =
ExternalApi.retrofitCall.getData(string).await()
if (dataList.intialDataResult < 1) {
return#withContext ResultState.Failure("Reason of error...")
} else {
database.myDataBase.insertAll(dataList)
return#withContext ResultState.Success
}
}
}
Now, report result to UI
fun onSearchButtonClicked(input: String) {
coroutineScope.launch {
val resultState = repo.insertToDatabase(input)
stateLiveData.value = resultState
}
}
In UI,
viewModel.stateLiveData.observe(viewLifeCycleOwner, Observer { state ->
when (state) {
is ResultState.Success -> { /* show success in UI */ }
is ResultState.Failure -> { /* show error in UI with state.message variable */ }
}
})
Similarly, you can add a ResultState.PROGRESS to show that a task is running in the UI.
If you have any queries, please add a comment.
I have a confusion about how Dispatchers work in Kotlin
Task
In my Application class I intend to access my database via Room, take out the user , take out his JWT accessToken and set it in another object that my retrofit Request inteceptor uses.
However I want all this code to be blocking , so that when the Application class has ran to its completion , the user has been extracted and set in the Inteceptor.
Problem
My application class runs to completion BEFORE the user has been picked from the database.
Session class is the one which accesses Room
This is how my session class looks
class Session(private val userRepository: UserRepository, private var requestHeaders: RequestHeaders) {
var authenticationState: AuthenticationState = AuthenticationState.UNAUTHENTICATED
var loggedUser: User? by Delegates.observable<User?>(null) { _, _, user ->
if (user != null) {
user.run {
loggedRoles = roleCsv.split(",")
loggedRoles?.run {
if (this[0] == "Employer") {
employer = toEmployer()
} else if (this[0] == "Employee") {
employee = toEmployee()
}
}
authenticationState = AuthenticationState.AUTHENTICATED
requestHeaders.accessToken = accessToken
}
} else {
loggedRoles = null
employer = null
employee = null
authenticationState = AuthenticationState.UNAUTHENTICATED
requestHeaders.accessToken = null
}
}
var loggedRoles: List<String>? = null
var employee: Employee? = null
var employer: Employer? = null
init {
runBlocking(Dispatchers.IO) {
loggedUser = userRepository.loggedInUser()
Log.d("Session","User has been set")
}
}
// var currentCity
// var currentLanguage
}
enum class AuthenticationState {
AUTHENTICATED, // Initial state, the user needs to secretQuestion
UNAUTHENTICATED, // The user has authenticated successfully
LOGGED_OUT, // The user has logged out.
}
This is my Application class
class MohreApplication : Application()
{
private val session:Session by inject()
private val mohreDatabase:MohreDatabase by inject() // this is integral. Never remove this from here. This seeds the data on database creation
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#MohreApplication)
modules(listOf(
platformModule,
networkModule,
....
))
}
Log.d("Session","Launching application")
}
My Koin module which creates the session
val platformModule = module {
// single { Navigator(androidApplication()) }
single { Session(get(),get()) }
single { CoroutineScope(Dispatchers.IO + Job()) }
}
In my Logcat first "Launching Application" prints out and THEN "User has been set"
Shouldn't it be reverse? . This is causing my application to launch without the Session having the user and my MainActivity complains.
by inject() is using kotlin lazy initialization. Only when session.loggedUser is queried will the init block be fired.
In your case, when you call session.loggedUser in the MainActivity, the init block will fire and block the calling thread.
What you can do is.
import org.koin.android.ext.android.get
class MohreApplication : Application()
{
private lateinit var session: Session
private lateinit var mohreDatabase: MohreDatabase // this is integral. Never remove this from here. This seeds the data on database creation
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#MohreApplication)
modules(listOf(
platformModule,
networkModule,
....
))
}
session = get()
mohreDatabase = get()
Log.d("Session","Launching application")
}
So if I press the Start button I want to repeat the function "fetchCarData()" every Second till I press the End Button. What's the best way to achieve this, is there any fancy Kotlin way of implementing such a thing?
class CarDataSourceImpl(private val carDataService: CarDataService) : CarDataSource {
//Live Data List that can be accessed only by this class
private val _loadedCarData = MutableLiveData<CarResponse>()
//actual Live Data List observed by the Views
override val loadedCarData: LiveData<CarResponse>
get() = _loadedCarData
//Fetch new Data and notify Observers via Live Data
override suspend fun fetchCarData() {
try {
val fetchedCarData = carDataService
.getData()
.await()
_loadedCarData.postValue(fetchedCarData)
} catch (e: NoConnectivityException) {
Log.e("Connectivity", "No Connection", e)
}
}
}
var timer=Timer()
private fun callfunctioneverysecond() {
val minTask=object : TimerTask() {
override fun run() {
//call function here
}
}
timer=Timer()
timer.schedule(minTask, 0L, 1000 * 1)
// timer.cancel()//call timer.cancel when click End button
}