My ViewModel function
patchProfileEmailAddress fuction wants the emailAddress variable
'
private fun saveUserEmailChanges(email: String?) {
profileRepository.patchProfileEmailAddress(emailAddress)
.onEach {
when (it) {
is Result.Success -> {
setLoading(false)
emailAddress = email
updateActionState(
MyProfilePersonInformationASMActionState.DismissBottomSheet)
updateActionState(MyProfilePersonInformationASMActionState.OnSuccess)}
is Result.Error -> {
setLoading(false)
updateActionState(
MyProfilePersonInformationASMActionState
.ShowErrorMessage(it.errorResponse?.message))}
is Result.Loading -> setLoading(true)} }
.launchIn(viewModelScope)}'
My Fragment part
'
var usersNewMail : String? =null
private fun setOnClickListeners() {
binding.apply {
adressArrowImageView.setOnClickListener{ openBodyBottomSheet() }
mailArrowImageView.setOnClickListener{ clickMailArrowImageView() }
checkOkeyImageView.setOnClickListener{ clickOkeyCheckImageView() }}}
private fun getMailChange(){
viewModel.saveUserEmailChanges(usersNewMail)
}
private fun clickMailArrowImageView(){
binding.apply {
txtEditMail.isEnabled = true
checkOkeyImageView.isVisible = true
mailArrowImageView.isVisible = false
}
}
private fun clickOkeyCheckImageView(){
binding.apply {
txtEditMail.isEnabled = false
checkOkeyImageView.isVisible = false
mailArrowImageView.isVisible = true
usersNewMail = txtEditMail.text.toString()
getMailChange()
}
}'
Postman works fine. In application patch response 500 Internal Server Error. My API wants string and I'm giving string.
It's certain you are sending something wrong if it works in Postman, so the first you have to do in any case is to know what you are sending; which can be done in various ways.
For example, if you are using OkHttp then have an interceptor for logging.
This way you can tell in LogCat what's going on.
After finding out what you are sending, if you still need help just update your question and I'll update my answer.
Related
So I am using the Google's API and for some reason, I'm getting a generic error:
E/Network: searchBooks: Failed Getting books
When it initially loads up, the hard coded query "android" shows up with a list of books associated with the book topic. But when I search up a different topic like "shoes" for example, the error shows up. Even when you hard code a different topic other than "android", it still shows the error. I have checked the API and it is working properly with the different query searches.
Here's the Retrofit Interface:
#Singleton
interface BooksApi {
#GET(BOOK_EP)
suspend fun getAllBooks(
//don't initialize the query, so that the whole api is available to the user
#Query("q") query: String
): Book
#GET("$BOOK_EP/{bookId}")
suspend fun getBookInfo(
#Path("bookId") bookId: String
): Item
}
The Repo
class BookRepository #Inject constructor(private val api: BooksApi) {
suspend fun getBooks(searchQuery: String): Resource<List<Item>> {
return try {
Resource.Loading(data = true)
val itemList = api.getAllBooks(searchQuery).items
if(itemList.isNotEmpty()) Resource.Loading(data = false)
Resource.Success(data = itemList)
}catch (exception: Exception){
Resource.Error(message = exception.message.toString())
}
}
suspend fun getBookInfo(bookId: String): Resource<Item>{
val response = try {
Resource.Loading(data = true)
api.getBookInfo(bookId)
}catch (exception: Exception){
return Resource.Error(message = "An error occurred ${exception.message.toString()}")
}
Resource.Loading(data = false)
return Resource.Success(data = response)
}
The ViewModel:
class SearchViewModel #Inject constructor(private val repository: BookRepository): ViewModel(){
var list: List<Item> by mutableStateOf(listOf())
var isLoading: Boolean by mutableStateOf(true)
init {
loadBooks()
}
private fun loadBooks() {
searchBooks("android")
}
fun searchBooks(query: String) {
viewModelScope.launch(Dispatchers.Default) {
if (query.isEmpty()){
return#launch
}
try {
when(val response = repository.getBooks(query)){
is Resource.Success -> {
list = response.data!!
if (list.isNotEmpty()) isLoading = false
}
is Resource.Error -> {
isLoading = false
Log.e("Network", "searchBooks: Failed Getting books", )
}
else -> {isLoading = false}
}
}catch (exception: Exception){
isLoading = false
Log.d("Network", "searchBooks: ${exception.message.toString()}")
}
}
}
}
I'll leave the project public so you guys can check it out for more of an understanding
Github Link: https://github.com/OEThe11/ReadersApp
P.S. you would have to create a login (takes 30 sec), but once you do, you'll have access to the app immediately.
This issue is occurring because of JsonSyntaxException java.lang.NumberFormatException while the JSON response is getting parsed from the API. This is because the averageRating field in the VolumeInfo data class is declared as Int but the response can contain floating point values.
If you change averageRating field type from Int to Double in the VolumeInfo data class, the exception would no longer occur.
I suggest you to debug your code in such cases.
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.
I am fairly new to this kotlin-coroutine thing and i have an issue with job-scheduling.In this code below, first i fetch topic names from user's cache in the fragment.(topicsList)
And then, i need to fetch these topics from API one by one. What i want to do is loop through the topicsList, make a request for each topic and get all the responses once at the completion of all requests. In order to achieve that, in getEverything() method(which fires up a request), i am adding the responses into an arraylist for every time.(responseList)
In for loop, i am firing up all the requests. After the completion of the job, job.invokeOnCompletion{} is called and i set my liveData to responseList. However, this approach doesn't work. Problem is, i am updating the liveData before the setting the responseList. I don't know how can it be possible. Could anybody help me about this?
Here is my CoroutineScope in myFragment:
val topicsList = dataMap["topics"] // GOT THE TOPICS
topicsList?.let {
var job: Job
CoroutineScope(Dispatchers.Main).launch {
job = launch {
for (topic in topicsList) {
mViewModel.getEverything(topic, API_KEY)
}
}
job.join()
job.invokeOnCompletion {
mViewModel.updateLiveData()
}
}
} ?: throw Exception("NULL")
getEverything() method in viewModel:
suspend fun getEverything(topic: String, apiKey: String) {
viewModelScope.launch {
_isLoading.value = true
withContext(Dispatchers.IO) {
val response = api.getEverything(topic, apiKey)
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
if (response.body() != null) {
responseList.add(response.body()!!)
println("Response is successful: ${response.body()!!}")
_isLoading.value = false
_isError.value = false
}
}
else {
Log.d(TAG, "getEverything: ${response.errorBody()}")
_isError.value = true
_isLoading.value = false
}
}
}
}
}
And, updateLiveData method:
fun updateLiveData() {
_newsResponseList.value = responseList
println("response list : ${responseList.size}")
responseList.clear()
}
And this is how it looks in the logs: Logs
Logs for you who cannot open the image :
I/System.out: response list : 0
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=techcrunch, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=the-verge, ...
Btw data is fetched without an error and its correct. I've no issue with that.
The issue is that getEverything uses launch to create a background job, then returns before it knows the job is complete.
To fix this, have getEverything return the data directly:
suspend fun getEverything(topic: String, apiKey: String): Response? {
_isLoading.value = true
val response = withContext(Dispatchers.IO) {
api.getEverything(topic, apiKey)
}
_isLoading.value = false
return response.takeIf { it.isSuccessful }?.body()?.let { body ->
println("Response is successful: $body")
}.also {
_isError.value = it == null
}
}
In your Fragment, request the results and assign them:
lifecycleScope.launch {
_responseList.value = topicsList.mapNotNull { topic ->
model.getResponse(topic, apiKey)
}
}
I'm new to RxJava and after a few days of trying everything I could find online I see that I really need help with this one.
I fetch a member in my repository with local and remote sources. I added some operators to return my remote source in priority (via debounce), and to filter out errors so it would return only 1 of the 2 if either remote is not available or the database is empty.
It works fine as long as something is returned by one of my 2 sources, but the problem occurs if both sources returns errors: as I filter out the errors, it doesn't return anything, and my subscribe is never called.
Maybe there is a simple solution but I have not found it so far, could someone help?
Here is my fetchMember() in my Repository:
override fun fetchMember(): Observable<MemberModel?> {
return Observable.concatArrayDelayError(memberLocalSource.fetchMember(), memberRemoteSource.fetchMember())
.doOnNext { member ->
saveMember(member!!)
}
.materialize()
.filter { !it.isOnError }
.dematerialize { it -> it }
.debounce(400, TimeUnit.MILLISECONDS)
}
And here is my viewmodel:
fun fetchToken(username: String, password: String) {
val loginDisposable = authApiService.loginWithJWT(username, password)
.flatMap {
isAuthenticated = isTokenValid(username, password, it)
sharedPreferences.setHasValidCredentials(isAuthenticated)
memberRepository.fetchMember()
}
.subscribeOn(Schedulers.io())
.observeOn((AndroidSchedulers.mainThread()))
.doOnError { throwable ->
throwable.printStackTrace()
}
.subscribe(
{ member ->
memberLiveData.value = member
this.memberId = member!!.id.toString()
this.memberName = member.name.split(" ")[0]
if(isAuthenticated) {
authenticationState.value = AuthenticationState.AUTHENTICATED_VALID_MEMBER
} else {
authenticationState.value = AuthenticationState.UNAUTHENTICATED_VALID_MEMBER
}
},
{ error ->
if(isAuthenticated) {
authenticationState.value = AuthenticationState.AUTHENTICATED_INVALID_MEMBER
} else {
authenticationState.value = AuthenticationState.INVALID_AUTHENTICATION
}
})
disposable.add(loginDisposable)
}
private fun isTokenValid(username: String, password: String, authResponse: AuthModel): Boolean {
return if (authResponse.data != null) {
false
} else {
tokenInterceptor.token = authResponse.token
val tokenWithCredentials = AuthModel(authResponse.token, null, null, username, password)
tokenRepository.saveToken(tokenWithCredentials)
true
}
}
In the end I managed to make it work by adding:
.defaultIfEmpty(MemberModel(-1))
and checking against id == -1.