Paging Library Using Post Not Get - Adapter Not Populated - android

I'm trying to use the new paging library for Android coding in Kotlin but am really stuck at the moment. My backend uses post method for connecting with the api calls and I'm trying to adapt the tutorials I've found using get but not being successful so far. Any help much appreciated indeed.
That's how my adapter is being called from my Fragment class but it's being always null.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setVerticalRecyclerView(rv_resources)
val itemViewModel = ViewModelProviders.of(this).get(ItemViewModel::class.java)
val adapter = ResourcesAdapter(activity as MainActivity)
itemViewModel.itemPagedList.observe(this, object : Observer<PagedList<Resource>> {
override fun onChanged(items: PagedList<Resource>?) {
adapter.submitList(items)
}
})
rv_resources.adapter = adapter
}
I feel the problem is probably coming from here:
class ItemDataSource : PageKeyedDataSource<Int, Resource>() {
override fun loadInitial(params: PageKeyedDataSource.LoadInitialParams<Int>, callback: PageKeyedDataSource.LoadInitialCallback<Int, Resource>) {
getResources()
}
override fun loadBefore(params: PageKeyedDataSource.LoadParams<Int>, callback: PageKeyedDataSource.LoadCallback<Int, Resource>) {
getResources()
}
override fun loadAfter(params: PageKeyedDataSource.LoadParams<Int>, callback: PageKeyedDataSource.LoadCallback<Int, Resource>) {
getResources()
}
private fun getResources() {
val jo = JsonObject()
jo.addProperty("page", 0)
jo.addProperty("page_size", 10)
GetAllResourceListAPI.postData(jo, object : GetAllResourceListAPI.ThisCallback {
override fun onSuccess(getResourceList: GetResourceList) {
Toast.makeText(App.getContext(), "onSuccess ${getResourceList.count}", Toast.LENGTH_SHORT).show()
}
override fun onFailure(failureMessage: String) {
Toast.makeText(App.getContext(), "onFailure", Toast.LENGTH_SHORT).show()
}
override fun onError(errorMessage: String) {
Toast.makeText(App.getContext(), "onError", Toast.LENGTH_SHORT).show()
}
})
}
Initially I'm trying to show any page within my adapter that's why pasted the same codes for loadInitial, loadBefore and loadAfter trying to tackle a problem at time if possible as currently my adapter shows empty even though I get a success from my api call. I may be missing something pretty obvious here but just can't see it as it's my first time using pagination and not very familiar with observers either.
I have a gist with a bit more of my code created here
Thanks very much for your help.

Related

RecyclerView interface for onclicklinester not working

Pleas can someone tell me why onSelect is not working when i click in something?
i passed many hours trying to solve this but cant find a good solution
RecyclerView fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerviewCriptomoedasMenu.adapter = CryptoCardListAdapter(
cryptoCards(),
object : CryptoCardListAdapter.OnSelectOnClickListener {
override fun onSelect(position: Int) {
when (cryptoCards()[position].coinTitle) {
"Bitcoin" -> {
val direction =
InvestimentosFragmentDirections.actionInvestimentosFragmentToAddFragment(
cryptoCards()[position]
)
findNavController().navigate(direction)
}
else -> {
val direction =
InvestimentosFragmentDirections.actionInvestimentosFragmentToAddFragment(
cryptoCards()[position]
)
findNavController().navigate(direction)
}
}
}
})
}
It is hard to detect your problem while lacking of your adapter implementation for the onSelect callback inside the listener. But there are some samples for handling on item click for Recyclerview that you can reference.
From Udacity
From official Android

Spinner Issue in MVVM architecture android

When I rotate the screen the spinner reset though I am using MVVM architecture.
While setting value I set value in view model, but still spinner reset to its orignal state.
In Main Activity I have done this,
GetBusinessPartners.setOnItemSelectedListener(object:OnItemSelectedListener{
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
dealMealPreApproval.initsetSpinnerIndex(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
})
dealMealPreApproval.getSpinnerValue().observe(this#DealMealPreApproval, Observer {
GetBusinessPartners.setSelection(it)
})
in view model i have done this
class MealPolicyViewModel : ViewModel() {
var businessPartners=MutableLiveData<ArrayList<BusinessPartnersModel>>()
var spinnerString=MutableLiveData<Int>()
fun initsetSpinnerIndex(valueOfSpinner:Int){
spinnerString.value=valueOfSpinner
Log.d("valueOfValueOFSPinner",valueOfSpinner.toString())
}
fun getSpinnerValue() : LiveData<Int>{
return spinnerString
}
}
For A small data like double, boolean, string, int you should use onSavedInstance like this, for large amount of data view model will be used.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("MySPinner", GetBusinessPartners.getSelectedItemPosition());
}
Then getValue Like this in OnCreate Method
var counter=0
if (savedInstanceState != null) {
counter = savedInstanceState.getInt("MySPinner", 0)
}
After Spinner Adapter call SetSelection and pass counter in it like,
ArrayAdapter<BusinessPartnersModel>(context, android.R.layout.simple_list_item_1, list)
GetBusinessPartners.setSelection(counter)
I still recommend you to use viewmodel with livedata in this case. Please check my solution.
In the viewmodel, you create the livedata that you want to store the data to display on the view. I still recommend using MutableLiveData to set data for live data, and LiveData for view to get data.
class MealPolicyViewModel : ViewModel() {
private val _businessPartners = MutableLiveData<ArrayList<BusinessPartnersModel>>()
val businessPartners: LiveData<ArrayList<BusinessPartnersModel>> = _businessPartners
private val _spinnerString = MutableLiveData<Int>()
val spinnerString: LiveData<Int> = _spinnerString
fun initsetSpinnerIndex(valueOfSpinner: Int){
_spinnerString.value = valueOfSpinner
Log.d("valueOfValueOFSPinner", valueOfSpinner.toString())
}
}
In the view, specifically MainActivity, you initialize the viewModel through the lazy variable associated with the built-in extension of the activity-ktx library by viewModels(). Then you observe your livedata in onCreate().
class MainActivity : AppCompatActivity() {
private val viewModel: MealPolicyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.spinnerString.observe(this) {
// TODO do something.
}
}
}
As you know, livedata will always observe the lifecycle of the view. In case you rotate the screen, the livedata will observe again when you finish rotating the screen.
Try my implementation and let me know if you still get the error.
save the position in viewmodel,did u try that?but do not call it in spinner adapter after item selected, save it in onPasue and the save button(or ending to network button).
this is how u get position:
binding.spinnerLessonModelName.selectedItemPosition

Live Data Observer called only once. It is not updating the data from server when api is called again to update UI

I looked for many articles and tried to understand how Live Data is observe changes when MVVM architecture is used.
I have a Fragment A, ViewModel and Repository class.
ViewModel is initiated in onCreateView() method of the fragment.
Api call is initiated just after that in onCreateView() method of fragment.
Data from the Server is observed in onViewCreated method of the fragment.
For the first, it is running perfectly fine. But When I update the user name from another Fragment B and come back to Fragment A.
Api is called again in onResume() method of Fragment A to update UI. But here my Live Data is not observed again and UI is not updated
I didn't understand what I am doing wrong? Why observer is not triggering second time?
Below is the code
class FragmentA : Fragment(){
private lateinit var dealerHomeViewModel: DealerHomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_home_dealers, container, false)
val dealerHomeFactory = DealerHomeFactory(token!!)
dealerHomeViewModel = ViewModelProvider(this,dealerHomeFactory).get(DealerHomeViewModel::class.java)
dealerHomeViewModel.getDealerHomeData()
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dealerHomeViewModel.dealerInfoLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer {dealerInfo ->
// Update UI
tvDealerName.text = dealerInfo.name
})
}
override fun onResume() {
super.onResume()
dealerHomeViewModel.getDealerHomeData()
}
}
//=========================== VIEW MODEL ===================================//
class DealerHomeViewModel(val token:String) : ViewModel() {
var dealerInfoLiveData:LiveData<DealerInfo>
init {
dealerInfoLiveData = MutableLiveData()
}
fun getDealerHomeData(){
dealerInfoLiveData = DealerHomeRepo().getDealerHomePageInfo(token)
}
}
//======================== REPOSITORY ================================//
class DealerHomeRepo {
fun getDealerHomePageInfo(token:String):LiveData<DealerInfo>{
val responseLiveData:MutableLiveData<DealerInfo> = MutableLiveData()
val apiCall: ApiCall? = RetrofitInstance.getRetrofit()?.create(ApiCall::class.java)
val dealerInfo: Call<DealerInfo>? = apiCall?.getDealerInfo(Constants.BEARER+" "+token,Constants.XML_HTTP)
dealerInfo?.enqueue(object : Callback<DealerInfo>{
override fun onFailure(call: Call<DealerInfo>, t: Throwable) {
Log.d(Constants.TAG,t.toString())
}
override fun onResponse(call: Call<DealerInfo>, response: Response<DealerInfo>) {
if(response.isSuccessful){
when(response.body()?.status){
Constants.SUCCESS -> {
responseLiveData.value = response.body()
}
Constants.FAIL -> {
}
}
}
}
})
return responseLiveData
}
}
I think your problem is that you are generating a NEW mutableLiveData each time you use your getDealerHomePageInfo(token:String method.
First time you call getDealerHomePageInfo(token:String) you generate a MutableLiveData and after on onViewCreated you observe it, it has a value.
In onResume, you call again getDealerHomePageInfo(token:String) that generates a NEW MutableLiveData so your observer is pointing to the OLD one.
What would solve your problem is to pass the reference of your viewModel to your repository so it updates the MutableLiveData with each new value, not generate a new one each time.
Edited Answer:
I would do something like this for ViewModel:
class DealerHomeViewModel(val token:String) : ViewModel() {
private val _dealerInfoLiveData:MutableLiveData<DealerInfo> = MutableLiveData()
val dealerInfoLiveData:LiveData = _dealerInfoLiveData
fun getDealerHomeData(){
DealerHomeRepo().getDealerHomePageInfo(token, _dealerInfoLiveData)
}
}
And this for the DealerHomeRemo
class DealerHomeRepo{
fun getDealerHomePageInfo(token:String, liveData: MutableLiveData<DealerInfo>){
val apiCall: ApiCall? = RetrofitInstance.getRetrofit()?.create(ApiCall::class.java)
val dealerInfo: Call<DealerInfo>? = apiCall?.getDealerInfo(Constants.BEARER+" "+token,Constants.XML_HTTP)
dealerInfo?.enqueue(object : Callback<DealerInfo>{
override fun onFailure(call: Call<DealerInfo>, t: Throwable) {
Log.d(Constants.TAG,t.toString())
}
override fun onResponse(call: Call<DealerInfo>, response: Response<DealerInfo>) {
if(response.isSuccessful){
when(response.body()?.status){
Constants.SUCCESS -> {
liveData.value = response.body()
}
Constants.FAIL -> {
}
}
}
}
})
}
For Observers, use the LiveData as before:
dealerHomeViewModel.dealerInfoLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer {dealerInfo ->
// Update UI
tvDealerName.text = dealerInfo.name
})

Kotlin ViewModel onchange gets called multiple times when back from Fragment (using Lifecycle implementation)

I am working with the MVVM architecture.
The code
When I click a button, the method orderAction is triggered. It just posts an enum (further logic will be added).
ViewModel
class DashboardUserViewModel(application: Application) : SessionViewModel(application) {
enum class Action {
QRCODE,
ORDER,
TOILETTE
}
val action: LiveData<Action>
get() = mutableAction
private val mutableAction = MutableLiveData<Action>()
init {
}
fun orderAction() {
viewModelScope.launch(Dispatchers.IO) {
// Some queries before the postValue
mutableAction.postValue(Action.QRCODE)
}
}
}
The fragment observes the LiveData obj and calls a method that opens a new fragment. I'm using the navigator here, but I don't think that the details about it are useful in this context. Notice that I'm using viewLifecycleOwner
Fragment
class DashboardFragment : Fragment() {
lateinit var binding: FragmentDashboardBinding
private val viewModel: DashboardUserViewModel by lazy {
ViewModelProvider(this).get(DashboardUserViewModel::class.java)
}
private val observer = Observer<DashboardUserViewModel.Action> {
// Tried but I would like to have a more elegant solution
//if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED)
it?.let {
when (it) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.action.observe(viewLifecycleOwner, observer)
// Tried but still having the issue
//viewModel.action.reObserve(viewLifecycleOwner, observer)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
// Tried but still having the issue
//viewModel.action.removeObserver(observer)
}
private fun navigateToQRScanner() {
log("START QR SCANNER")
findNavController().navigate(LoginFragmentDirections.actionLoginToPrivacy())
}
}
The problem
When I close the opened fragment (using findNavController().navigateUp()), the Observe.onChanged of DashboardFragment is immediately called and the fragment is opened again.
I have already checked this question and tried all the proposed solutions in the mentioned link (as you can see in the commented code). Only this solution worked, but it's not very elegant and forces me to do that check every time.
I would like to try a more solid and optimal solution.
Keep in mind that in that thread there was no Lifecycle implementation.
The issue happens because LiveData always post the available data to the observer if any data is readily available. Afterwords it will post the updates. I think it is the expected working since this behaviour has not been fixed even-though bug raised in issue tracker.
However there are many solutions suggested by developers in SO, i found this one easy to adapt and actually working just fine.
Solution
viewModel.messagesLiveData.observe(viewLifecycleOwner, {
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
//Do your stuff
}
})
That's how LiveData works, it's a value holder, it holds the last value.
If you need to have your objects consumed, so that the action only triggers once, consider wrapping your object in a Consumable, like this
class ConsumableValue<T>(private val data: T) {
private val consumed = AtomicBoolean(false)
fun consume(block: ConsumableValue<T>.(T) -> Unit) {
if (!consumed.getAndSet(true)) {
block(data)
}
}
}
then you define you LiveData as
val action: LiveData<ConsumableValue<Action>>
get() = mutableAction
private val mutableAction = MutableLiveData<ConsumableValue<Action>>()
then in your observer, you'd do
private val observer = Observer<ConsumableValue<DashboardUserViewModel.Action>> {
it?.consume { action ->
when (action) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
UPDATE
Found a different and still useful implementation of what Frances answered here. Take a look

Kotlin-android-extension communication between Classes similar to Communicating with other Fragments

I have 3 Parts to my Project: A Model that does calculations, some Fragments that display the UI and send Trigger to my third part, the main activity. I did all my Fragments with some interfaces like Communicating with Other Fragments.
However now I need one of the part of my Model to trigger some UI changes. And I don't know how to do that. Because my goal is to have one part of my Model send or trigger some functions so that the GUI gets updated but it doesn't know the GUI by itself. (it is totally independent from it)
In Main activity I override all the functions
class MainActivity : AppCompatActivity(), MimaFragment.elementSelectedListener, InstructionFragment.instructionSaveButtonClickedCallback , OptionFragment.optionSaveButtonClickedCallback, MimaFragment.UITrigger{
override fun abortOptions() {
extendNormal()
}
override fun updateMima() {
mimaFragment.updateView()
}
override fun normal() {
mimaFragment.drawArrows()
}}
Fragment exapmle:
class OptionFragment : Fragment() {
var optionCallback : optionSaveButtonClickedCallback? = null
interface optionSaveButtonClickedCallback{
fun updateMima()
fun abortOptions()
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
view?.findViewById(R.id.optionsAbort)?.setOnClickListener{
optionCallback?.abortOptions()
}
}
override fun onAttach(context: Context?) {
super.onAttach(context)
try {
optionCallback = context as optionSaveButtonClickedCallback
} catch (e : ClassCastException){
throw ClassCastException(activity.toString() + " must implementoptionSaveButtonClickedCallback")
}
}
}
That is how you usually do it and it works fine. Now to my Question is there a way to do it just like that for a non Fragment class? I tryed it like this:
class MimaModul(name: String, description : String, context: Context) : Element(name, description) {
val uiTrigger : UITrigger? = null
init{
try {
uiTrigger = context as UITrigger
} catch (e : ClassCastException){
Log.d("ClassCastException","Didn't implement uiTrigger")
}
}
fun step(){
//it does some stuff here and then calls for example
uiTrigger?.normal()
}
interface UITrigger{
fun normal()
}
}
However as I expected the UITrigger cast does not work. (it always catches an exception) Do you have any ideas how to solve this. Or how else to do it?
ideally I want MimaFragment to implement the interface. But that didn't work either.
class MimaFragment : Fragment(), MimaModul.UITrigger {
//other stuff
override fun normal() {
drawArrows()
}
}
So when ever my Model is done with a step it should trigger some UI change. And I tryed to avoid just doing a loop and update all Elements based on their status because that would take forever. (Though I see this as my only options at the moment)
Let me know if I was unclear and i shall elaborate.

Categories

Resources