Repeat function to call data from server every second - android

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
}

Related

Wait for first Flow emit and update second Flow whenever there is a change on first one

I implemented an API call and I would like to allow the user to choose the sort order, keeping the choice saved in Datastore Preferences.
But I couldn't implement this idea, since both are returning a Flow, I don't know how to make one Flow wait for the other, and whenever the first one emits a new value, update the second.
Some example code I'm trying:
interface PreferencesRepository {
fun getFilter(): Flow<OrderType>
}
class PreferencesRepositoryImpl #Inject constructor() : PreferencesRepository {
override fun getFilter(): Flow<OrderType> = flow {
delay(timeMillis = 300) // simulate initial read delay from datastore
emit(value = OrderType.ASCENDING)
delay(timeMillis = 3000) // simulate a order change after 3 secs
emit(value = OrderType.DESCENDING)
}
}
interface ItemsRepository {
fun getItems(order: OrderType): Flow<List<ItemModel>>
}
class ItemsRepositoryImpl #Inject constructor() : ItemsRepository {
private val dummyItems: List<ItemModel> = listOf(
ItemModel(id = 1, title = "first item"),
ItemModel(id = 2, title = "second item"),
ItemModel(id = 3, title = "third item")
)
override fun getItems(order: OrderType): Flow<List<ItemModel>> = flow {
delay(timeMillis = 1000) // simulate network call delay
when (order) {
OrderType.ASCENDING -> {
emit(value = dummyItems)
}
OrderType.DESCENDING -> {
emit(value = dummyItems.sortedByDescending { it.id })
}
}
}
}
#HiltViewModel
class HomeViewModel #Inject constructor(
private val itemsRepository: ItemsRepository,
private val preferencesRepository: PreferencesRepository
) : ViewModel() {
private val _filter = MutableLiveData<OrderType>()
val filter: LiveData<OrderType> get() = _filter
private val _items = MutableLiveData<List<ItemModel>>()
val items: LiveData<List<ItemModel>> get() = _items
init {
// i imagine that these two methods must be unified somehow to work
getFilter()
getItems()
}
private fun getFilter() = viewModelScope.launch {
preferencesRepository.getFilter().collectLatest { orderType ->
_filter.value = orderType
println(orderType.name)
}
}
private fun getItems() = viewModelScope.launch {
itemsRepository.getItems(
order = _filter.value ?: OrderType.ASCENDING // not updated as expected
).collectLatest { items ->
_items.value = items
items.forEach { println(it.title) }
}
}
}
Logcat of current code:
You can use either flatMapLatest() or alternatively flatMapConcat() operator:
preferencesRepository.getFilter()
.flatMapLatest { itemsRepository.getItems(it) }
Whenever there is a new order emitted, it invokes getItems() with this order and then emits items from getItems().
flatMapLatest() only cares about the last order emitted. If new order is emitted before items for the last one are acquired, it just ignores the previous order(s) and cancels fetching of items for it (or don't even start fetching). It seems like this is what you need.
flatMapConcat() always invokes getItems() for each new order and waits for items before processing the next ordering.
Also, if this is your real case and not a simplified example for StackOverflow, then I suggest to not re-fetch items when ordering changes. You can re-order locally.

Share a queue between the mainThread and a coroutine

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

Firebase make 3 requests at a time MVVM

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.

Live Data return old response then New response always in toast in User Registration using Retrofit

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.

How to handle errors with liveData

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.

Categories

Resources