How to create list view/grid view/recycler view with retrofit API in jetpack compose

Viewed 826

I have created Kotlin Code for parsing APIs with retrofit in list view/grid view/recycler view, I wanted to know how can I do the same using jetpack compose? I have used retrofit to parse GET API responses using different ViewGroups. View Binding is used to interact with the views on this screen.

Code

 import android.annotation.SuppressLint
                import android.app.AlertDialog
                import android.app.ProgressDialog
                import android.content.ActivityNotFoundException
                import android.content.DialogInterface
                import android.content.Intent
                import android.content.res.Configuration
                import android.os.Bundle
                import android.util.Log
                import android.view.*
                import android.view.inputmethod.EditorInfo
                import android.widget.*
                import androidx.activity.viewModels
                import androidx.appcompat.app.AppCompatActivity
                import androidx.databinding.DataBindingUtil
                import okhttp3.ResponseBody
                import org.json.JSONArray
                import org.json.JSONException
                import org.json.JSONObject
                import retrofit.Retrofit2
                import retrofit2.Call
                import retrofit2.Callback
                import retrofit2.Response
                import retrofit2.Retrofit
                import retrofit2.converter.gson.GsonConverterFactory
                import supports.*
                import viewmodel.SIViewModel
                import java.io.IOException
                import java.net.SocketTimeoutException
                import java.util.*
                
                
                class TestIndex : AppCompatActivity() {
                    var adapter: Adapter1? = null
                    var dialog: AlertDialog? = null
                    var builder: AlertDialog.Builder? = null
                    private val viewModel: SIViewModel? by viewModels()
                    var test_arr = ArrayList<TestModel>()
                    var binding: TestGridBinding? = null
               
                
                    @SuppressLint("CommitPrefEdits", "ClickableViewAccessibility", "SetTextI18n")
                    override fun onCreate(savedInstanceState: Bundle?) {
                        super.onCreate(savedInstanceState)
                        binding = DataBindingUtil.setContentView(this, R.layout.test_grid)
                        setSupportActionBar(binding?.view?.toolbarr)
                        supportActionBar!!.elevation = 0f
                
                        viewModel
                
                
                       
                        adapter = Adapter1(this@TestIndex, R.layout.row, test_arr)
                
                
                
        //binding ViewModel retrofit API with activity, here ID1 and ID2 coming from the previous screen.
                        viewModel!!.getList(this@TestIndex , ID1!!, ID2!!)
        
                        binding?.gvTest?.adapter = adapter
                
        
                        binding?.swipeRefreshLayout?.setOnRefreshListener {
                            binding?.swipeRefreshLayout?.isRefreshing = true
                 
                                if (ID1 != null && ID2 != null) {
        
        // getting same server response on swipe refresh widget
                                    getdata(ID1!!, ID2!!)
                                } else {
                                    builder = AlertDialog.Builder(MyApplication.instance)
                                    builder!!.setCancelable(false)
                                    builder!!.setTitle("Alert")
                                    builder!!.setNegativeButton("Cancel") { dialog: DialogInterface, which: Int ->
                                        dialog.dismiss()
                                        finish()
                                    }
                                    builder!!.setPositiveButton("OK") { dialog: DialogInterface, which: Int -> dialog.dismiss() }
                                    dialog = builder!!.create()
                                    dialog?.show()
                                }
                
                        }
                
                
                
                        subscribeObservers()
                    }
                
//this is checked on the dev portal but I don't know I could I use it //dynamically with adapters and ArrayList.
                    @Composable
        fun LazyRowItemsDemo() {
            LazyRow {
                items((1..title_arr.size).toList()) {
                    Text(text = "Item $it")
                }
            }
        }
                
                
                    private fun getdata(id1: String, id2: String) {
                
                        val mProgressDialog = ProgressDialog(this@TestIndex)
                        mProgressDialog.isIndeterminate = true
                        mProgressDialog.setMessage(Keys.KEY_pre_msg)
                        if (!this.isFinishing) {
                            mProgressDialog.show()
                        }
                        val retrofit = Retrofit.Builder()
                            .baseUrl(Keys.testURL)
                            .client(OkHttpClient().build())
                            .addConverterFactory(GsonConverterFactory.create())
                            .build()
                        val retrofitInterface = retrofit.create(
                            RetrofitInterface::class.java
                        )
                        val call = retrofitInterface.getTestdata(id1, id2)
                
                        call!!.enqueue(object : Callback<ResponseBody?> {
                            override fun onResponse(call: Call<ResponseBody?>, response: Response<ResponseBody?>) {
                                var remoteResponse: String? = null
                                if (response.code() == 200) {
                                    try {
                                        assert(response.body() != null)
                                        remoteResponse = response.body()!!.string()
                                    } catch (e: Exception) {
                                        e.printStackTrace()
                                    }
                                } else {
                                    try {
                                        if (response.errorBody() != null) {
                                            remoteResponse = response.errorBody()!!.string()
                                        }
                                    } catch (e: IOException) {
                                        e.printStackTrace()
                                    }
                                }
                                if (remoteResponse != null) {
            
            //getting response fields and parsing list view or grid view/recycler view in different screens
            
                                        adapter =
                                            Adapter1(this@TestIndex, R.layout.row, test_arr)
                                        binding!!.gvTest.adapter = adapter
                                        adapter!!.notifyDataSetChanged()
                
                                }
                            }
                
                            override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
                                Log.d(Keys.KEY_TAG, "onFailure: " + t.localizedMessage)
                            }
                        })
                        if (mProgressDialog.isShowing) mProgressDialog.dismiss()
                    }
                
                
        //subscribed the Observers here from view model
                    private fun subscribeObservers() {
                        viewModel!!.lifting.observe(this, { TestModel: List<TestModel>? ->
                            adapter!!.updateTests(TestModel)
                            binding!!.swipeRefreshLayout.isRefreshing = false
                    }
                
                }

Kindly let me know how can I do the same using jetpack compose for listview, grid view, recycler view. Thanks.

1 Answers

It's more a general example, without retrofit. You can implement your data fetch inside my getTestData method.

To begin with, in order to understand the basic principles of working with Compose, I suggest you study compose tutorials.

Compose uses view models to perform complex data manipulations. I will use the basic version, but you can also check out Hilt for more complex architecture.

In order for changing the state of an object to lead to a recomposition, you can use:

  1. The mutableStateObject - this is a specially created container for compose that will update the view if the value has changed
  2. you can also use LiveData and Flow, they can both be cast to mutableStateObject.

Note that mutableStateObject will not alert you to changes in the container object fields if you pass a complex class there. It will only notify you when the value itself changes, so it is recommended to use it only for simple types.

You can also use mutableStateListOf to store collections. In my example you will see both: with mutableStateListOf it is convenient to add/delete objects to the collection, while mutableStateObject with List lying inside is easier to completely replace with new objects.

Inside Composable functions you need to wrap your mutable state objects with remember to prevent reinitializing them on each composition, and inside your view model you don't need to do that, because it's not gonna be reinitialized in any case.

SwipeRefresh is not part of compose, it's a library made by compose maintainers too. To install it follow this instructions.

I'm using two columns here just to show difference between mutableStateOf and mutableStateListOf, you can remove Row and one of LazyColumn

class ScreenViewModel : ViewModel() {
    var list by mutableStateOf(emptyList<String>())
    var mutableList = mutableStateListOf<String>()
    var isRefreshing by mutableStateOf(false)

    init {
        refresh()
    }

    fun refresh() {
        isRefreshing = true
        viewModelScope.launch {
            list = getTestData()
            mutableList.addAll(0, list)
            isRefreshing = false
        }
    }

    suspend fun getTestData(): List<String> {
        // emulate network call
        delay(1000)
        return List(100) {
            Random.nextInt(100).toString()
        }
    }
}

@Composable
fun TestView() {
    val viewModel: ScreenViewModel = viewModel()
    SwipeRefresh(
        state = rememberSwipeRefreshState(viewModel.isRefreshing),
        onRefresh = {
            viewModel.refresh()
        },
    ) {
        Row {
            LazyColumn(
                modifier = Modifier.weight(1f) // this needed inly for two columns case
            ) {
                itemsIndexed(viewModel.list) { i, item ->
                    Text("$i $item")
                }
            }
            LazyColumn(
                modifier = Modifier.weight(1f) // this needed inly for two columns case
            ) {
                itemsIndexed(viewModel.mutableList) { i, item ->
                    Text("$i $item")
                }
            }
        }
    }
}

Result:

Related