So I have this function created to generate fake values for my list:
private fun generateFakeValues(): List<Torrent> {
val values = mutableListOf<Torrent>()
val torrent1 = Torrent()
torrent1.name = "Big Buck Bunny"
torrent1.downloadSpeed = 0.00
torrent1.uploadSpeed = 0.00
torrent1.downloaded = 59.23
torrent1.length = 263.64
values.add((torrent1))
return values
}
And it works just fine. Now I added a Http request and wanted to parse the data but the items are not in the list:
private fun getTorrents(): List<Torrent> {
var torrents = mutableListOf<Torrent>()
val request = Request.Builder()
.url("...")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
throw IOException("Unexpected code $response")
}
torrents = gson.fromJson(response.body!!.string(), mutableListOf<Torrent>()::class.java)
}
}
})
return torrents
}
What am I doing wrong?
Since network call happens on background thread. You have to publish data to UI thread like
activity.runOnUiThread({/*add to adapter and notify data change to adapter*/})
or
view.post({/*add to adapter and notify data change to adapter*/})
Related
I am using the mvvm architecture. I first call retrofit to get the data via a view model.
val retrofit = Retrofit.Builder()
.baseUrl("http://hp-api.herokuapp.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(HarryPotterApi::class.java)
val call = service.staff()
try {
call.enqueue(object : Callback<Staff> {
override fun onResponse(call: Call<Staff>, response: Response<Staff>) {
println("the response code is " +response.code())
if (response.code() == 200) {
val characterData = response.body()!!
test ="response is good"
println("response is good + The first name is " + characterData[0].name)
pictureList.add(characterData[0].name)
actorList.add(characterData[0].name)
characterList.add(characterData[0].name)
houseList.add(characterData[0].name)
}
}
override fun onFailure(call: Call<Staff>, t: Throwable) {
}
})}catch (e: IOException) {
e.printStackTrace()
}
}
I then pass in the data into a recycler view in the main activity
fun createRV( pictureList: ArrayList<String?>, actorList: ArrayList<String>,
characterList: ArrayList<String?>, houseList: ArrayList<String?>){
val recyclerview = findViewById<RecyclerView>(R.id.recyclerview)
recyclerview.layoutManager = LinearLayoutManager(this)
val data = ArrayList<ItemsViewModel>()
data.add(ItemsViewModel(R.drawable.ic_launcher_background, actorList[0] ,"fds","gsd"))
val adapter = RVAdapter(data)
recyclerview.adapter = adapter
}
My api call does work as i can print out the data.
Can you add the stack trace? but probably its one of those places where you try to access an array or list at index 0, you should check if the array is empty first
Maybe response.body() is null or empty so you should check it and let do stuff
Cause : Retrofit cannot parse data in your data model(Staff)
val characterData = response.body()
if (characterData.isNullOrEmpty()) {
println("List is null or empty")
} else {
pictureList.add(characterData[0].name)
actorList.add(characterData[0].name)
characterList.add(characterData[0].name)
houseList.add(characterData[0].name)
}
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 want to do a list of calls to upload a list of pictures, show a progress dialog at the beginning and dismiss it at the end. Howewer the progress dialog never show up. If I comment progresRing.dismiss() then the dialog appear but later. Is there a better way to do multiple calls than in a for loop ?
val progresRing = ProgressDialog(this#AddExtraPicturesActivity)
progresRing.isIndeterminate = true
progresRing.setTitle("Uploading pictures")
progresRing.setMessage("Please wait")
progresRing.setCancelable(false)
progresRing.show()
for (item in pictureList) {
if(item.pictureFile != null) {
val file = item.pictureFile
if(file!!.exists()) {
var fileData = Base64.encodeToString(FileUtils.readFileToString(file).toByteArray(), Base64.DEFAULT)
val transactionId = UUID.randomUUID().toString()
val tokenId = ""
val jobDocument = JobDocument("Test", "", "", "PHONE_PICTURE", "jpg", "test.jpg", "", fileData)
val requestBody = UploadDocumentRequest("Test", jobDocument)
val service = RestAPI(this#AddExtraPicturesActivity)
val request = service.uploadDocument(authorization, transactionId, tokenId, requestBody)
request.enqueue(object : Callback<UploadDocumentResponse> {
override fun onResponse(call: Call<UploadDocumentResponse>, response: Response<UploadDocumentResponse>) {
Timber.d( response.toString())
}
override fun onFailure(call: Call<UploadDocumentResponse>, t: Throwable) {
Timber.d(t.toString())
}
})
}
}
}
progresRing.dismiss()
The best way to achieve this is definitely by using Reactive Programming so you could have some sort of callback when all calls are done to perform another action.
An easier way would be to count the total number of calls you need to make and do the following:
// find here the total of calls you need to make before the loop
totalCount = ??
var = 0
// and later, as retrofit requests are asynchronous, on the last upload the condition will be valid and the progress should dismiss
request.enqueue(object : Callback<UploadDocumentResponse> {
override fun onResponse(call: Call<UploadDocumentResponse>, response: Response<UploadDocumentResponse>) {
Timber.d( response.toString())
var = var + 1
if(var == totalCount)
progresRing.dismiss()
}
override fun onFailure(call: Call<UploadDocumentResponse>, t: Throwable) {
Timber.d(t.toString())
}
})
I'm doing self-learning of android (mostly), and I'm can't grasp a concept about Testing. I tried googling and youtube but still don't get it, so I really need a sample to test my code below
Could anyone show me how to create unit test request data to server from this code?
fun fetchJSON() {
val url =baseurl + prevnext + idliga
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
val gson = GsonBuilder().create()
val DataMatch= gson.fromJson(body, DataPertandingan::class.java)
runOnUiThread {
rvPrevMatch.adapter= PrevAdapter(DataMatch)
}
}
})
}
And about the instrumented test, how do I test adding something to SQLite.
here is my activity code of adding data to SQLite.
private fun addToFavorite() {
try {
database.use {
insert(
Favorite.DATA_FAVORITE,
Favorite.ID_EVENT to id_event,
Favorite.DATE to tanggaltandingdet.text,
// home team
Favorite.HOME_ID to idhome,
Favorite.HOME_TEAM to timkandangdet.text,
Favorite.HOME_SCORE to skorkandangdet.text,
Favorite.HOME_GOAL_DETAILS to cetakgolkandang.text,
Favorite.HOME_LINEUP_GOALKEEPER to kiperkandang.text,
Favorite.HOME_LINEUP_DEFENSE to bekkandang.text,
Favorite.HOME_LINEUP_MIDFIELD to midkandang.text,
Favorite.HOME_LINEUP_FORWARD to strikerkandang.text,
Favorite.HOME_LINEUP_SUBSTITUTES to cadangankandang.text,
// Favorite.HOME_TEAM_BADGE to urllogokandang.text,
// away team
Favorite.AWAY_ID to idaway,
Favorite.AWAY_TEAM to timtandangdet.text,
Favorite.AWAY_SCORE to skortandangdet.text,
Favorite.AWAY_GOAL_DETAILS to cetakgoltandang.text,
Favorite.AWAY_LINEUP_GOALKEEPER to kipertandang.text,
Favorite.AWAY_LINEUP_DEFENSE to bektandang.text,
Favorite.AWAY_LINEUP_MIDFIELD to midtandang.text,
Favorite.AWAY_LINEUP_FORWARD to strikertandang.text,
Favorite.AWAY_LINEUP_SUBSTITUTES to cadangantandang.text
// Favorite.AWAY_TEAM_BADGE to urllogotandang.text
)
}
toast ("Data Telah Di Simpan" )
} catch (e: SQLiteConstraintException) {
toast("Error: ${e.message}")
}
}
I call the getFavorite method to get data from the Database Class in Activity. But It doesn't wait for onCompleteListener. So The list comes as empty. What should I do?
GetMovieActivity.java
private fun getPhotoListFromApi() {
val apiService = ApiClient.getRetrofitInstance().create(ApiService::class.java)
var list = ArrayList<MovieDetailsResponse>()
var db = Database()
db.getFavorite()
for (id in db.moviesID)
{
apiService.getMovieDetails(
id.toString().toInt(),
"922df43f1a304aca901feb9728b01943",
Locale.getDefault().language + "-" + Locale.getDefault().country)
.enqueue(object : Callback<MovieDetailsResponse> {
override fun onFailure(call: Call<MovieDetailsResponse>, t: Throwable) {
}
override fun onResponse(call: Call<MovieDetailsResponse>, response: Response<MovieDetailsResponse>) {
response.body()?.let { it ->
list.add(it)
}
}
})
}
adapter.setList((list as List<MovieListItem>?)!!)
}
Database.kt
fun getFavorite()
{
val dr = db.collection("Favorites").document(mAuth!!.uid) as DocumentReference
dr.get().addOnCompleteListener { task: Task<DocumentSnapshot> ->
if(task.isSuccessful)
{
moviesMap = task.result.data!!
moviesID = moviesMap.get("movies") as ArrayList<Any>
}
}
}
In you'r adapter you need to snd the new list always and refresh your adapter by calling notifyDataSetChange()
and in once the callback finish you can ether you use liveData to post data to it and listen to it and push the new data to the adapter and refresh it to make sure you always having the right and latest data and you can use rxJava to make sure you're working in background thread and once it finish push it to ui thread and update you'r adapter