How should I implement my AsyncTask class? - android

I am making a weather app, where I use AsyncTask for getting response from API and after that setting up the UI. Here how looks like my code now after simplyfing:
class MainActivity : AppCompatActivity() {
/*
SOME INSIGNIFICANT CODE HERE
*/
private fun setUI(currentWeather: Root){
tv_city.text = "${currentWeather.name}, ${currentWeather.sys.country}"
/*
...
*/
}
inner class WeatherByNameTask: AsyncTask<String, Unit, Unit>(){
override fun doInBackground(vararg p0: String?) {
val city: String? = p0[0]
val call = weatherApi.getCurrentWeatherByCityName(city!!, API_KEY, "metric")
call.enqueue(object: Callback<Root>{
override fun onResponse(call: Call<Root>, response: Response<Root>) {
if (!response.isSuccessful){
Toast.makeText(this#MainActivity, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
} else {
val currentWeather = response.body()
setUI(currentWeather!!)
}
}
override fun onFailure(call: Call<Root>, t: Throwable) {
Toast.makeText(this#MainActivity, "Code: ${t.message}", Toast.LENGTH_LONG).show()
}
})
}
}
inner class WeatherByCoordTask: AsyncTask<Location, Unit, Unit>(){
override fun doInBackground(vararg p0: Location?) {
val lat: String = p0[0]?.latitude.toString()
val lon: String = p0[0]?.longitude.toString()
val call = weatherApi.getCurrentWeatherByCoordinates(lat, lon, API_KEY, "metric")
call.enqueue(object: Callback<Root>{
#SuppressLint("SetTextI18n")
override fun onResponse(call: Call<Root>, response: Response<Root>) {
if (!response.isSuccessful){
Toast.makeText(this#MainActivity, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
} else {
val currentWeather = response.body()
setUI(currentWeather!!)
}
}
override fun onFailure(call: Call<Root>, t: Throwable) {
Toast.makeText(this#MainActivity, "Code: ${t.message}", Toast.LENGTH_LONG).show()
}
})
}
}
}
It works, but I'm getting a warning:
This AsyncTask class should be static or leaks might occur
I want to make it in correct way. I tried to implement it outside the MainActivity class, passing the Context as a parameter, but what's about setUI function? I guess making it public is bad idea.

This AsyncTask class should be static or leaks might occur
In the MainActivity, there are 2 AsyncTask class with inner modifier, this means the inner class will keep a strong reference to the outer class. The warning tells you while the AsyncTask is doing its job in the background, if the user leaves the current activity (press Back key or calling finish() method), then the activity instance will be leaked because the AsyncTask still keeps a strong reference to it.
Solution
Using WeakReference to let the AsyncTask keeps a weak reference to the MainActivity.
class WeatherByNameTask (activity: MainActivity): AsyncTask<String, Unit, Unit>(){
private val activityRef = WeakReference<MainActivity>(activity)
override fun doInBackground(vararg p0: String?) {
val city: String? = p0[0]
val call = weatherApi.getCurrentWeatherByCityName(city!!, API_KEY, "metric")
call.enqueue(object: Callback<Root>{
override fun onResponse(call: Call<Root>, response: Response<Root>) {
if (!response.isSuccessful){
activityRef.get()?.let {
Toast.makeText(it, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
}
} else {
val currentWeather = response.body()
activityRef.get()?.setUI(currentWeather!!)
}
}
override fun onFailure(call: Call<Root>, t: Throwable) {
activityRef.get().let {
Toast.makeText(it, "Code: ${t.message}", Toast.LENGTH_LONG).show()
}
}
})
}
}
class WeatherByCoordTask (activity: MainActivity): AsyncTask<Location, Unit, Unit>(){
private val activityRef = WeakReference<MainActivity>(activity)
override fun doInBackground(vararg p0: Location?) {
val lat: String = p0[0]?.latitude.toString()
val lon: String = p0[0]?.longitude.toString()
val call = weatherApi.getCurrentWeatherByCoordinates(lat, lon, API_KEY, "metric")
call.enqueue(object: Callback<Root>{
#SuppressLint("SetTextI18n")
override fun onResponse(call: Call<Root>, response: Response<Root>) {
if (!response.isSuccessful){
activityRef.get()?.let {
Toast.makeText(it, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
}
} else {
val currentWeather = response.body()
activityRef.get()?.setUI(currentWeather!!)
}
}
override fun onFailure(call: Call<Root>, t: Throwable) {
activityRef.get().let {
Toast.makeText(it, "Code: ${t.message}", Toast.LENGTH_LONG).show()
}
}
})
}
}
Use from the activity
val weatherByNameTask = WeatherByNameTask(this)
val weatherByCoordTask = WeatherByCoordTask(this)

Following is the way how to make AsyncTask :
private class AsyncTaskGetPlaces extends AsyncTask<Void, Void, AsyncTaskResult<Object>>
{
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected AsyncTaskResult<Object> doInBackground(Void... params)
{
try
{
LibHttp libHttp = new LibHttp();
String res = libHttp.listBusiness("21","test#ns.com");
return new AsyncTaskResult<Object>(res);
}
catch (Exception e)
{
e.printStackTrace();
return new AsyncTaskResult<Object>(e);
}
}
#Override
protected void onPostExecute(AsyncTaskResult<Object> result)
{
if(result.getError()!= null)
{
showOKAlertMsg("App",getResources().getString(R.string.txt_data_not_found), false);
}
else
{
String res = result.getResult().toString();
try {
JSONObject resObj = new JSONObject(res);
if(resObj.getString("status_code").equals("1")){
//parse
// Do your task here
}
} catch (JSONException e) {
e.printStackTrace();
showOKAlertMsg("",getResources().getString(R.string.txt_internal_server_error), false);
}
}
}
}
Where AsyncTaskResult is
public class AsyncTaskResult<T>
{
private T result;
private Exception error;
public T getResult()
{
return result;
}
public Exception getError()
{
return error;
}
public AsyncTaskResult(T result)
{
this.result = result;
}
public AsyncTaskResult(Exception error)
{
this.error = error;
}
}

Related

OkHttp requested XML data jumbled

I am requesting for a xml data, but the response was not correct. The response was jumbled.
Here are some of my code:
NetworkUtils:
object NetworkUtils {
private val client = OkHttpClient.Builder()
.cookieJar(object : CookieJar{
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return CookiesManager.getCookies()
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
CookiesManager.saveCookies(cookies)
}
})
.build()
fun getUrl(url: String, callback: Callback) {
val request = Request.Builder()
.url(url)
.get()
.build()
client.newCall(request).enqueue(callback)
}
...
}
VideoManager:
object VideoManager {
...
fun getDanmaku(cid : Long, callback: Callback){
NetworkUtils.getUrl("http://api.bilibili.com/x/v1/dm/list.so?oid=$cid", callback)
}
...
}
VideoPlayingFragment:
#SuppressLint("ResourceType")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val danmakuContext : DanmakuContext = DanmakuContext.create()
val maxLinesPair = HashMap<Int, Int>()
maxLinesPair[BaseDanmaku.TYPE_SCROLL_RL] = 5
maxLinesPair[BaseDanmaku.TYPE_SCROLL_LR] = 5
val overlappingEnablePair = HashMap<Int, Boolean>()
overlappingEnablePair[BaseDanmaku.TYPE_SCROLL_LR] = true
overlappingEnablePair[BaseDanmaku.TYPE_SCROLL_RL] = true
overlappingEnablePair[BaseDanmaku.TYPE_FIX_BOTTOM] = true
danmakuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3F) //设置描边样式
.setDuplicateMergingEnabled(false)
.setScrollSpeedFactor(1.2f) //是否启用合并重复弹幕
.setScaleTextSize(1.5f) //设置弹幕滚动速度系数,只对滚动弹幕有效
.setCacheStuffer(SpannedCacheStuffer()) // 图文混排使用SpannedCacheStuffer 设置缓存绘制填充器,默认使用{#link SimpleTextCacheStuffer}只支持纯文字显示, 如果需要图文混排请设置{#link SpannedCacheStuffer}如果需要定制其他样式请扩展{#link SimpleTextCacheStuffer}|{#link SpannedCacheStuffer}
.setMaximumLines(maxLinesPair) //设置最大显示行数
.preventOverlapping(overlappingEnablePair) //设置防弹幕重叠,null为允许重叠
val loader : ILoader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI)
VideoManager.getDanmaku((activity as VideoActivity).currentVideo.cid, object : Callback{
override fun onFailure(call: Call, e: IOException) {
mThreadPool.execute{
requireActivity().runOnUiThread{
Toast.makeText(requireContext(), "加载弹幕失败!", Toast.LENGTH_SHORT).show()
}
}
}
override fun onResponse(call: Call, response: Response) {
try{
val responseIs = response.body?.byteStream()
val str = String(responseIs?.readBytes()!!, Charsets.UTF_8)
Log.d(Application.getTag(), "onResponse: $str")
loader.load(str)
}
catch (e : IllegalDataException){
e.printStackTrace()
mThreadPool.execute{
requireActivity().runOnUiThread{
Toast.makeText(requireContext(), "加载弹幕失败!", Toast.LENGTH_SHORT).show()
}
}
}
val danmukuParser = BiliDanmukuParser()
val dataSource : IDataSource<*> = loader.dataSource
danmukuParser.load(dataSource)
binding.danmakuView.setCallback(object : DrawHandler.Callback{
override fun prepared() {
Log.d(Application.getTag(), "prepared: ")
VideoManager.getVideoUrl((activity as VideoActivity).currentVideo.bvid, (activity as VideoActivity).currentVideo.cid, object : Callback{
override fun onFailure(call: Call, e: IOException) {
mThreadPool.execute{
requireActivity().runOnUiThread{
Toast.makeText(requireContext(), "加载视频失败!", Toast.LENGTH_SHORT).show()
}
}
}
override fun onResponse(call: Call, response: Response) {
val responseString = response.body?.string()
mThreadPool.execute{
val videoUrls : VideoStreams = Gson().fromJson(responseString, VideoStreams::class.java)
requireActivity().runOnUiThread{
val uri : Uri = Uri.parse(videoUrls.data.durl[0].url)
val headers = HashMap<String, String>()
headers["User-Agent"] = "Mozilla/5.0 BiliDroid/*.*.* (bbcallen#gmail.com)"
headers["Referer"] = "https://bilibili.com/"
binding.videoView.setVideoURI(uri, headers)
binding.videoView.start()
val mediaController = MediaController(requireContext())
binding.videoView.setMediaController(mediaController)
//binding.danmakuView.
binding.danmakuView.start()
}
}
}
})
}
override fun updateTimer(timer: DanmakuTimer?) {
Log.d(Application.getTag(), "updateTimer: ")
}
override fun drawingFinished() {
Log.d(Application.getTag(), "drawingFinished: ")
}
})
binding.danmakuView.prepare(danmukuParser, danmakuContext)
binding.danmakuView.enableDanmakuDrawingCache(true) //true在模拟器上运行有问题
}
})
}
And here is one of my requested datas:
I've been searching on the internet but none of the method worked. Such as removing Accept-Encoding header. Can someone help me fix this? Thanks in advance.

getResponse From ViewModel Live Data

I was trying to get response from viewModel But, I am having hard time to use the response data on another activity to display it on a text view.
I have already setup the backend for the repo's and interfaces, data classes etc...
Thank you!!!
// View Model
class HomeActivityViewModel : ViewModel() {
lateinit var createPostLiveData: MutableLiveData<PostResponseData?>
init {
createPostLiveData = MutableLiveData()
}
fun getPostLiveObserver(): MutableLiveData<PostResponseData?> {
return createPostLiveData
}
fun createPostData(postdata: PostData) {
val retroService = RetrofitApiFactory.retroInstance().create(ChefApi::class.java)
val call = retroService.postData(postdata)
call.enqueue(object : Callback<PostResponseData> {
override fun onResponse(
call: Call<PostResponseData>,
response: Response<PostResponseData>
) {
if (response.isSuccessful) {
createPostLiveData.postValue(response.body())
var text = response.body()!!.choices[0].text
Log.d("response", text) // only shows the one in the viewModel
} else {
createPostLiveData.postValue(null)
Log.d("failed", response.errorBody().toString())
}
}
override fun onFailure(call: Call<PostResponseData>, t: Throwable) {
Log.d("failed", t.message.toString())
createPostLiveData.postValue(null)
}
})
}
}
Activity.kt
class HomeActivity : AppCompatActivity() {
lateinit var mAuth: FirebaseAuth
lateinit var viewModel: HomeActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home_actvity)
mAuth = FirebaseAuth.getInstance()
initViewModel()
generate.setOnClickListener {
createPost()
}
logout.setOnClickListener {
logoutUser()
}
}
private fun createPost() {
// creating a post
val prompt = "Some string..."
val postdata = PostData(120, prompt, 0.3, 1.0, 0.0)
viewModel.createPostData(postdata)
}
private fun initViewModel() {
// initialize view model
viewModel = ViewModelProvider(this).get(HomeActivityViewModel::class.java)
viewModel.getPostLiveObserver().observe(this, Observer<PostResponseData?> {
if (it == null) {
Toast.makeText(this#HomeActivity, "Failed to post data", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#HomeActivity, "Successfully posted data", Toast.LENGTH_SHORT)
.show()
}
})
}
private fun logoutUser() {
mAuth.signOut()
updateUI()
}
private fun updateUI() {
val intent = Intent(this#HomeActivity, MainActivity::class.java)
startActivity(intent)
finish()
}
Try to change your HomeActivityViewModel class and add a LiveData object to it:
class HomeActivityViewModel : ViewModel() {
var _createPostLiveData: MutableLiveData<PostResponseData?>()
// Live data instance
val createPostLiveData
get() = _createPostLiveData
fun createPostData(postdata: PostData) {
val retroService = RetrofitApiFactory.retroInstance().create(ChefApi::class.java)
val call = retroService.postData(postdata)
call.enqueue(object : Callback<PostResponseData> {
override fun onResponse(
call: Call<PostResponseData>,
response: Response<PostResponseData>
) {
if (response.isSuccessful) {
// Update live data value
_createPostLiveData.value = response.body()
var text = response.body()!!.choices[0].text
Log.d("response", text) // only shows the one in the viewModel
} else {
// Update live data value
_createPostLiveData.value = null
Log.d("failed", response.errorBody().toString())
}
}
override fun onFailure(call: Call<PostResponseData>, t: Throwable) {
Log.d("failed", t.message.toString())
// Update live data value
_createPostLiveData.value = null
}
})
}
}
You should then be able to observe the LiveData instance in your Activity:
class HomeActivity : AppCompatActivity() {
lateinit var mAuth: FirebaseAuth
// Initialize view model in declaration
private val viewModel: HomeActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home_actvity)
mAuth = FirebaseAuth.getInstance()
// Observe for `createPostLiveData` changes
viewModel.createPostLiveData.observe(this) {
if (it == null) {
Toast.makeText(this#HomeActivity, "Failed to post data", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#HomeActivity, "Successfully posted data", Toast.LENGTH_SHORT)
.show()
}
}
generate.setOnClickListener {
createPost()
}
logout.setOnClickListener {
logoutUser()
}
}
private fun createPost() {
// creating a post
val prompt = "Some string..."
val postdata = PostData(120, prompt, 0.3, 1.0, 0.0)
viewModel.createPostData(postdata)
}
...

ClassCastException in NetworkBoundResource<ResultType, RequestType>

I am following the "best practices"(?) from the GitHub sample: https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.kt
I am getting an error
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.xxx.yyy.data.model.TestUser
I've tried using Gson to convert to JSON and back to the Generic RequestType, but that doesn't work either. The data is coming back just fine. If I cast the response in the NetworkBoundResource, then it works - but that kind of defeats the purpose of generics. I am also using the aws-android-sdk to invoke lambda, which makes things a HUGE pain, but that's AWS for ya
TestUser
public class TestUser {
private String username;
public TestUser() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
NetworkBoundResource
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(private val appExecutors: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
Log.e("NetworkBoundResource", "fetching from network")
appExecutors.networkIO().execute {
fetchFromNetwork(dbSource)
}
} else {
Log.e("NetworkBoundResource", "not fetching from network")
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = MutableLiveData<ApiResponse<RequestType>>()
// This is super dumb, but can't createCall() on the main thread 🤦
var lambdaResponse: LambdaResponse<RequestType>? = null
var exception: Exception? = null
try {
lambdaResponse = createCall()
} catch (e: Exception) {
exception = e
}
appExecutors.mainThread().execute {
// Can't setValue on a background thread 🤦
if (exception != null) {
apiResponse.value = ApiResponse.create(exception)
} else if (lambdaResponse != null) {
apiResponse.value = ApiResponse.create(lambdaResponse)
}
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
appExecutors.diskIO().execute {
val x = processResponse(response)
// FAILING HERE
saveCallResult(x)
appExecutors.mainThread().execute {
// we specially request a new live data,
// otherwise we will get immediately last cached value,
// which may not be updated with latest results received from network.
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
appExecutors.mainThread().execute {
// reload from disk whatever we had
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
protected open fun onFetchFailed() {
Log.e("NetworkBoundResource", "onFetchFailed")
}
fun asLiveData() = result as LiveData<Resource<ResultType>>
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract fun saveCallResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): LiveData<ResultType>
#MainThread
protected abstract fun createCall(): LambdaResponse<RequestType>
}
LoginDataSource
fun auth(authRequest: AuthRequest): LiveData<Resource<User>> {
return object : NetworkBoundResource<User, TestUser>(appExecutors) {
override fun saveCallResult(item: TestUser) {
Log.e("username", item.username)
// Log.e("saveCallResult", "saving user to db: ${item.federatedIdentity}")
// userDao.insertAll(item)
}
override fun shouldFetch(data: User?): Boolean {
val fetch = data == null
Log.e("shouldFetch", "$fetch")
return true
}
override fun loadFromDb(): LiveData<User> {
Log.e("loadFromDb", "findById: ${authRequest.federatedIdentity}")
return userDao.findById(authRequest.federatedIdentity)
}
override fun createCall(): LambdaResponse<TestUser> {
Log.e("createCall", "authenticating user: ${authRequest.federatedIdentity}")
return authApiService.auth(authRequest)
}
}.asLiveData()
}

parse JSON object using retrofit in kotlin

I am trying to show json data using retrofit library in kotlin
This is my Json:
[
{
"login": "mojombo",
"id": 1,
},
{
"login": "defunkt",
"id": 2,
}
]
My Main activity
call.enqueue(object : Callback<UserResponse> {
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
Log.e("list","list")
val countrylist = response.body()
for (size in response.body()) {
System.out.println(size.toString())
}
// var listOfMovies: List<UserResponse> = response.body()?.results!!
// myCustomAdapter = UserListAdapter(applicationContext, listOfMovies)
// recyclerView.setAdapter(myCustomAdapter)
progressBar.visibility = View.GONE
}
override fun onFailure(call: Call<UserResponse>?, t: Throwable?) {
progressBar.visibility = View.GONE
Log.e("list", t.toString())
}
})
This an app that I build in kotlin using retrofit and rxjava in the best way possible using a test API.
Model
data class Post( val userID:Int, val title:String, val body: String)
Retrofit Package
IMyApi interface
interface IMyApi {
#get:GET("posts")
val posts: Observable<List<Post>>
}
RetrofitClient Object class
object RetrofitClient {
val instance: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Adapter Package
PostAdapter class
class PostAdapter(private val context: Context, private val postList: List<Post>)
:RecyclerView.Adapter<PostViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
PostViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.post_item, parent, false)
return PostViewHolder(itemView)
}
override fun getItemCount(): Int {
return postList.size
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int)
{
holder.userId.text = postList[position].userID.toString()
holder.title.text = postList[position].title
holder.body.text = StringBuilder(postList[position].body.substring(0,20))
.append("...").toString()
}
}
PostViewHolder class
class PostViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
var userId = itemView.txtID
var title = itemView.txtTitle
var body = itemView.txtBody
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var jsonApi: IMyApi
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Init api
val retrofit = RetrofitClient.instance
jsonApi = retrofit.create(IMyApi::class.java)
// View
recycler_posts.layoutManager = LinearLayoutManager(this)
recycler_posts.setHasFixedSize(true)
fetchData()
}
private fun fetchData() {
compositeDisposable.add(jsonApi.posts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { posts->displayData(posts)})
}
private fun displayData(posts: List<Post>?) {
val adapter = PostAdapter(this, posts!!)
recycler_posts.adapter = adapter
}
}
Using this as displayed above should help you solve your issue hopefully. Also when in the code you come across "recycler_posts". This is a id to the recycler added in activity_main. If you need me to include that let me know
That's what we have on our app
object GetFAQsAPI {
private val LOG_TAG = GetFAQsAPI.javaClass.simpleName
interface ThisCallback {
fun onSuccess(getFAQs: GetFAQs)
fun onFailure(failureMessage: String)
fun onError(errorMessage: String)
}
/* POST */
fun postData(jo: JsonObject, callback: GetFAQsAPI.ThisCallback) {
val call = Service.getService().get_faqs(jo)
call.enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
//Log.e(LOG_TAG, response.body().toString());
try {
if (response.body()?.get("success")!!.asBoolean) {
val gson = GsonBuilder().setPrettyPrinting().create()
val getFAQs = gson.fromJson(response.body(), GetFAQs::class.java)
callback.onSuccess(getFAQs)
} else {
Log.e(LOG_TAG, "else")
val error = response.body()!!.get("err").asString
callback.onError(error)
}
} catch (e: Exception) {
Log.e(LOG_TAG, "exception" + e.localizedMessage)
callback.onFailure(e.message!!)
}
}
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
Log.e(LOG_TAG, "onFailure: " + t.message)
callback.onFailure(t.message!!)
}
})
}
}
That's how we call it from our fragment - getFAQs is the object parsed.
private fun getFAQsAPI() {
showLoading(true)
val jo = JsonObject().apply {
addProperty("faq_category", "admin")
}
GetFAQsAPI.postData(jo, object : GetFAQsAPI.ThisCallback {
override fun onSuccess(getFAQs: GetFAQs) {
Log.i(LOG_TAG, "onSuccess")
showLoading(false)
updateUI(getFAQs)
}
override fun onFailure(failureMessage: String) {
Log.e(LOG_TAG, failureMessage)
}
override fun onError(errorMessage: String) {
Log.e(LOG_TAG, errorMessage)
}
})
}
Hope that helps.

How to show ProgressDialog when fetching data from ViewModel

I want to show ProgressDialog while fetching data from ViewModel and it works fine when I fetch data for the first time, but when I want to refresh the data from API the ProgressDialog starts and does not stops
I create MutableLiveData<Boolean>() and try to manage the visibility but it's not working
This is how i refresh my data from my Activity
private fun loadNorthTram() {
val model =
ViewModelProviders.of(this#MainActivity).get(MyViewModelNorth::class.java)
model.isNorthUpdating.observe(
this#MainActivity,
Observer { b ->
if (b!!)
AppUtil.showProgressSpinner(this#MainActivity)
else
AppUtil.dismissProgressDialog()
})
model.getNorthTrams().observe(this#MainActivity, Observer
{
if (it != null) {
setData(it)
}
})
}
Below is my ViewModel class
class MyViewModelNorth : ViewModel() {
private lateinit var mtoken: String
private val apiService: ApiInterface = ApiClient.client.create(ApiInterface::class.java)
private lateinit var trams: MutableLiveData<TramModel>
val isNorthUpdating = MutableLiveData<Boolean>().default(false)
fun getNorthTrams(): MutableLiveData<TramModel> {
isNorthUpdating.value = true
if (!::trams.isInitialized) {
trams = MutableLiveData()
callTokenAPI()
}
return trams
}
private fun callTokenAPI() {
val tokenObservable: Observable<TokenModel> = apiService.fetchToken()
tokenObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { self ->
mtoken = self.responseObject[0].DeviceToken
callTramAPI()
}
.subscribe(getTokenObserver())
}
private fun callTramAPI() {
val apiService: ApiInterface = ApiClient.client.create(ApiInterface::class.java)
val observable: Observable<TramModel> = apiService.fetchTrams(AppUtil.NORTH_TRAMS, mtoken)
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getTramObserver())
}
private fun getTokenObserver(): Observer<TokenModel> {
return object : Observer<TokenModel> {
override fun onComplete() {}
override fun onSubscribe(d: Disposable) {}
override fun onNext(tokenModel: TokenModel) {}
override fun onError(e: Throwable) {
if (e is HttpException) {
val errorBody = e.response().errorBody()
HttpErrorUtil(e.code()).handelError()
}
}
}
}
private fun getTramObserver(): Observer<TramModel> {
return object : Observer<TramModel> {
override fun onComplete() {
isNorthUpdating.value = false
}
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: TramModel) {
if (!t.hasError && t.hasResponse)
trams.value = t
else if (t.errorMessage.isBlank())
applicationContext().showToast(t.errorMessage)
else
applicationContext().showToast(applicationContext().getString(R.string.something_wrong))
}
override fun onError(e: Throwable) {
if (e is HttpException) {
val errorBody = e.response().errorBody()
HttpErrorUtil(e.code()).handelError()
}
}
}
}
public fun getIsNothUpdating(): LiveData<Boolean> {
return isNorthUpdating
}
fun <T : Any?> MutableLiveData<T>.default(initialValue: T) = apply { setValue(initialValue) }
}
I have not tested your code but I think your problem is in function getNorthTrams() in viewmodel.
First time when you fetch data, trams is not initialized, your api call is happening and at only onCompleted, you are setting isNorthUpdating.value = false. This code works.
But when you refresh data, trams is already initialized. So, there is no case for isNorthUpdating.value = false, which is causing progress dialog to not dismiss.
So I think you should handle else case in your viewmodel.
fun getNorthTrams(): MutableLiveData<TramModel> {
isNorthUpdating.value = true
if (!::trams.isInitialized) {
trams = MutableLiveData()
callTokenAPI()
}else{
//do your thing for refresh
isNorthUpdating.value = false
}
return trams
}
Also, in api call, if error occur, you should set isNorthUpdating to false and show some error message. Otherwise, progress dialog will always be showing even if some error occur in api call.

Categories

Resources