How to Pagination using retrofit to fetch next items from API on Scrolling in kotlin? - android-studio

I want to make my recyclerview paginationscrolling using retrofit.
I already complete get Json data using retrofit. It means interface API is correct from API Document.
However, if I loaded more 20items. can not scroll more items in Client.
when I checked Server data. per one page can get maximum 20items.
For example, if I loaded 25items in my recyclerview.
page 0: 20, page 1: 5.
if I want scrolling all items, How can I make paginationscrolling for retrofit??
check some my code and help me..
Response
Interface API
#GET("/store/cart/mine")
fun getCart(#Header("Authorization") token: String?, #Query("page") page:Int): Call<CartResponse>
CartViewActivity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val lastVisibleItemPosition: Int
get()= LinearLayoutManager.HORIZONTAL
private lateinit var scrollListener: RecyclerView.OnScrollListener
lateinit var mAdapter: CartItemRecyclerAdapter
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
val page = 0
val token = SharedPreference.getTokenInfo(this)
Client.retrofitService.getCart(token,page).enqueue(object :Callback<CartResponse> {
override fun onResponse(call: Call<CartResponse>, response: Response<CartResponse>) {
if (response?.isSuccessful == true) {
swipeRefreshLo.setOnRefreshListener(this#CartViewActivity)
showdata(response.body()?.docs!!)
}else if(response?.isSuccessful==false) {
val er = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java)
if (er.code==60202) {
}
}
}
override fun onFailure(call: Call<CartResponse>, t: Throwable) {
}
})
}
private fun showdata(results: MutableList<cartDocs>) {
recycler_view.apply {
mAdapter=CartItemRecyclerAdapter(context,context as Activity, results)
recycler_view.adapter=mAdapter
recycler_view.layoutManager=LinearLayoutManager(context)
setRecyclerViewScrollListener()
}
}
private fun setRecyclerViewScrollListener() {
scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val totalItemCount = recyclerView.layoutManager!!.itemCount
if(totalItemCount == lastVisibleItemPosition + 1) {
Log.d("MyTAG", "Load new list")
recyclerView.removeOnScrollListener(scrollListener)
}
}
}
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}

Firstly, you need to modify your adapter to be able to update existing data, instead of creating new adapter every time you fetch new data.
Adapter should be initialised before getting the data, to be able to call methods on it.
You should create a method inside the adapter, something like
fun updateData(results: MutableList<cartDocs>) {
dataSet.addAll(results)
notifyItemRangeInserted(start, newItemsSize)
}
Then, we you get response from server in onSuccess() you should call method above, and increment page, so the next time you load data, you get new items.
Data should be fetched first time when screen loads (page will be 0), and then when user scrolls to bottom of RV ().

Related

Invisible mode in Android studio (kotlin)

I'm working on a news app and I want my layout to become invisible when a user saves an article. Can anyone please tell me the right way to write this code?
2 things to note:
-when I run the app,the layout is visible in the "saved fragment"
but then when I add "hideSavedMessage" right next to the code that updates the recyclerView and I run the app, the layout becomes invisible.
I want the layout to be invisible only when the user saves an article.
PS: I know how the visible and invisible mode works. I have used it before. My major problem is not knowing the right place to write the code. And by layout, I mean the text view and image view that appears on the screen. I would appreciate any contributions. Thank you.
Here's my code
class SavedFragment : Fragment(R.layout.fragment_saved) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: SavedAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
setupRecyclerView()
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_savedFragment_to_savedArticleFragment,
bundle
)
}
val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position =
viewHolder.adapterPosition//get position of item we deleted so that we swipe to left or right
val article =
newsAdapter.differ.currentList[position]//from news adapter at the index of the position
viewModel.deleteArticle(article)
Snackbar.make(view, "Successfully deleted article", Snackbar.LENGTH_LONG)
.apply {
setAction("Undo") {
viewModel.saveArticle(article); hideSavedMessage()
}
show()
}
val isAtLastItem = position <= 0
val shouldUpdateLayout = isAtLastItem
if (shouldUpdateLayout) {
showSavedMessage()
}
}
}
ItemTouchHelper(itemTouchHelperCallback).apply {
attachToRecyclerView(rvSavedNews)
}
viewModel.getSavedNews().observe(viewLifecycleOwner, Observer { articles ->
newsAdapter.differ.submitList(articles)
})
}
private fun setupRecyclerView() {
newsAdapter = SavedAdapter()
rvSavedNews.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
}
}
private fun hideSavedMessage() {
savedMessage.visibility = View.INVISIBLE
isArticleAdded = false
}
private fun showSavedMessage() {
savedMessage.visibility = View.VISIBLE
}
}
The problem is that code inside observer runs even at the beginning - when you run your app, right? If I understand your problem, you just have to manage to make the fun hideSavedMessage() not be running for the first time. You could for example instantiate a boolean in onCreate() and set it to false. Then, inside the observer, you could run the hideSavedMessage() only if that boolean is true - you would set it as true at the end of the observer. I hope you understand.

How to get the text from Listview to output to my external txt file

I'm new here and I'm new to programming in general.
I followed a YouTube video to make a simple todo list app for android and have since modified it into a visitors register.
I am copying people who sign in to an external txt file (that part is ok because I can grab the information as it's entered) but I have been struggling for about a week now to try to get the names of people who are signing out.
Basically a name and date/time get written to myExternalFile on btnAddTodo
is it possible in the btnDeleteDoneTodo to grab the names from deleteDoneTodos() and add them to myEternalFile??
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
todoAdapter = TodoAdapter(mutableListOf())
rvTOdoItems.adapter = todoAdapter
rvTOdoItems.layoutManager = LinearLayoutManager(this)
val fileName = "log.txt"
val filePath = "Sign In\\"
val myExternalFile = File (getExternalFilesDir(filePath),fileName)
btnAddTodo.setOnClickListener {
val todoTitle = etTodoTitle.text.toString().uppercase()
val regTitle = etRegTitle.text.toString().uppercase()
if (todoTitle.isNotEmpty() && regTitle.isNotEmpty()) {
val todo = Todo(todoTitle+"\n"+regTitle)
myExternalFile.appendText(todoTitle +" "+ regTitle +" "+ todoAdapter.getCurrentDateTime() +"\n")
todoAdapter.addTodo(todo)
etTodoTitle.text.clear()
etRegTitle.text.clear()
}
}
btnDeleteDoneTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
}
}
class Todo (
val title : String,
var isChecked: Boolean = false
class TodoAdapter(
val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.todo,
parent,
false
)
)
}
fun getCurrentDateTime() : Date {
return Calendar.getInstance().time
}
fun addTodo (todo : Todo) {
todos.add(todo)
notifyItemInserted(todos.size-1)
}
fun deleteDoneTodos () {
todos.removeAll(Todo::isChecked)
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvtodoTitle: TextView, isChecked: Boolean) {
if (isChecked) {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
} else {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val curTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = curTodo.title
cdDone.isChecked = curTodo.isChecked
toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
cdDone.setOnCheckedChangeListener { _, isChecked ->
toggleStrikeThrough(tvTodoTitle, isChecked)
curTodo.isChecked = !curTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
I have tried appending just about every variable I think would be relevant with .toString() in the btnDeleteDoneTodos method, I have tried making separate lists in the deleteDoneTodos function, I tried making a new function that outputs a list but I have failed.
I don't know if it's even possible with the way it has all been implemented - like I said I'm very new to programming so any help would be appreciated.
Thanks
edit.
I have managed to grab a single person leaving by adding the following leavers function to the todoAdapter class:
fun leavers (tvtodoTitle: TextView, isChecked: Boolean) : String {
var going = ""
if (isChecked) going = tvtodoTitle.text.toString()
return going
}
which returns a single string (hence only one person) that I can then append to my external file. I have tried to change the function to mutable list then iterate through it in btnDeleteDoneTodo listener but it will still only return one name.
I think I'm going to can this and go with Dewerros suggestion and try to setup a local Db, if anybody does have a solution it would still be gladly received as this has annoyed me for far too long now.

E/RecyclerView: No adapter attached; skipping layout / Kotlin

I have been reading the different answers here on stackoverflow and on this blog post and tried to implement their solutions but I am still getting the error. The code is from a yt tutorial. I hope someone can help me. Thanks
Error E/RecyclerView: No adapter attached; skipping layout
My Adapter
This is my Main Activity
class MainActivity : AppCompatActivity(), IDrinkLoadListener {
lateinit var drinkLoadListener: IDrinkLoadListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
loadDrinkFromFirebase()
}
private fun loadDrinkFromFirebase() {
val drinkModels : MutableList<DrinkModel> = ArrayList()
FirebaseDatabase.getInstance()
.getReference("Drink")
.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()) {
for (drinkSnapshot in snapshot.children) {
val drinkModel = drinkSnapshot.getValue(DrinkModel::class.java)
drinkModel!!.key = drinkSnapshot.key
drinkModels.add(drinkModel)
}
drinkLoadListener.onDrinkLoadSuccess(drinkModels)
} else
drinkLoadListener.onDrinkLoadFailed("Drink items not exist")
}
override fun onCancelled(error: DatabaseError) {
drinkLoadListener.onDrinkLoadFailed(error.message)
}
})
}
private fun init() {
drinkLoadListener = this
val gridLayoutManager = GridLayoutManager(this, 2)
recycler_drink.layoutManager = gridLayoutManager
recycler_drink.addItemDecoration(SpaceItemDecoration())
}
override fun onDrinkLoadSuccess(drinkModelList: List<DrinkModel>?) {
val adapter = MyDrinkAdapter(this,drinkModelList!!)
recycler_drink.adapter = adapter
}
override fun onDrinkLoadFailed(message: String?) {
Snackbar.make(mainLayout,message!!,Snackbar.LENGTH_LONG).show()
}
}
class SpaceItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
if(parent.getChildLayoutPosition(view) % 2 != 0)
{
outRect.top= 25
outRect.bottom= -25
}else outRect.top = 0
}
}
Thanks in advance!!!
That happens because the adapter is attached to the recycler after an asynchronous operation on onDrinkLoadSuccess
You can attach the adapter to the recycler during onCreate and then the onDrinkLoadSuccess update it with the data.
The ListAdapter class has a submit method for it. If you are using plain recycler is usually something like this:
Recycler ... {
private val dataList = mutableListOf()
fun update(list: List<YourType>) {
dataList.clear()
dataList.addAll(list)
notifyDataDetChanged() //important updates the UI
}
}
There are more specific updates methods like notifyItemRangeChanged, etc.
Also there are helpers for finding difference in the data sets DiffUtil.ItemCallback

How to randomly navigate to other fragments?

I have 20 fragments. I want to navigate fragments randomly with a button.It works correctly but I don't want to revisit the same fragments. If I navigate from fragment 5 to 9, I don't want to re-navigate to 5 anymore. I want to see every fragment just once. In every fragment I did this:
btnNavigateNewFragment.setOnClickListener {
val list: MutableList<Int> = mutableListOf(
R.id.nav_new_game_eighteen,
R.id.nav_dealgame,
R.id.nav_new_game_one,
R.id.nav_new_game_two,
R.id.nav_new_game_three,
R.id.nav_new_game_four,
R.id.nav_new_game_five,
R.id.nav_new_game_six,
R.id.nav_new_game_seven,
R.id.nav_new_game_eight,
R.id.nav_new_game_nine,
R.id.nav_new_game_ten,
R.id.nav_new_game_eleven,
R.id.nav_new_game_twelve,
R.id.nav_new_game_thirteen,
R.id.nav_new_game_fourteen,
R.id.nav_new_game_fifteen,
R.id.nav_new_game_sixteen,
R.id.nav_new_game_seventeen
)
val randomFragment= list.random()
findNavController().navigate(randomFragment)
What logic should I use to not visit the fragment that navigated once ?
I would keep track of the visited fragments in an Activity-scoped ViewModel. If you keep it in a Fragment or Activity property, it will be lost if there's a configuration change.
class MyViewModel: ViewModel() {
private val destinations = listOf(
R.id.nav_new_game_eighteen,
R.id.nav_dealgame,
R.id.nav_new_game_one,
//...
R.id.nav_new_game_seventeen
)
private val remainingDestinations = mutableListOf<Int>().apply { addAll(destinations) }
fun getNextNavDestination(): Int {
if (remainingDestinations.isEmpty()) {
remainingDestinations.addAll(destinations)
// or whatever logic you want to do when all destinations have been used
}
val destination = remainingDestinations.random()
remainingDestinations.remove(destination)
return destination
}
}
In your fragment that goes to one of the random destinations:
class MyFragment: Fragment() {
private val myViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
//...
btnNavigateNewFragment.setOnClickListener {
indNavController().navigate(myViewModel.getNextNavDestination())
}
}
}

Can't communication between DialogFragment and Activity using Observer pattern?

When you press the button to open a separate input window, there is a function to display the results toast.
class MainActivity : AppCompatActivity() {
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val f = TestPopup()
usingRxJava(f)
//usingLiveData(f)
}
}
private fun usingRxJava(f: TestPopup) {
val subject = SingleSubject.create<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
subject.onSuccess(str)
}
}
subject.subscribe({
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}, {
}).addTo(disposable)
}
private fun usingLiveData(f: TestPopup) {
val liveData = MutableLiveData<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
liveData.postValue(str)
}
}
liveData.observe(this, Observer {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
})
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
}
DialogFragment
class TestPopup : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_test, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val arg = Bundle()
arg.putString(TEST_KEY, edit_test.text.toString())
arguments = arg
dismiss()
}
}
companion object {
const val TEST_KEY = "KEY"
}
}
(Sample Project Url : https://github.com/heukhyeon/DialogObserverPattern )
This sample code works in normal cases. However, the toast does not float after the following procedure.
Developer Option - Dont'keep activities enable
Open TestPopup, and enter your text. (Do not press OK button)
Press the home button to move the app to the background
The app is killed by the system.
Reactivate the app (Like clicking on an app in the apps list)
In this case, the text I entered remains on the screen, but nothing happens when I press the OK button.
Of course I know this happens because at the end of the activity the observe relationship between the activity and the Dialog is over.
Most of the code uses the implementation of the callback interface for that Dialog in the Activity to handle that case.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val input = edit_test.text.toString()
(activity as MyListener).inputComplete(input)
dismiss()
}
}
class MainActivity : AppCompatActivity(), TestPopup.MyListener {
override fun inputComplete(input: String) {
Toast.makeText(this, "Accept : $input", Toast.LENGTH_SHORT).show()
}
}
But I think it's a way that doesn't match the Observer pattern, and I want to implement it using the Observer pattern as much as possible.
I'm thinking of getting a Fragment from the FragmentManager and subscribing again at onCreate, but I think there's a better way.
Can someone help me?
Your understanding of the problem is correct, except that the problem happens with any configuration changes, including screen rotation. You can reproduce issue without using the developer mode. Try this for example:
Open TestPopup, and enter your text. (Do not press OK button)
Rotate screen
See toast message not popping up.
Also note that your "observer pattern" implementation is not a proper observer pattern. Observer pattern has a subject and an observer. In your implementation, the activity is acting as both the subject and the observer. The dialog is not taking any part in this observer pattern, and using .setOnDismissListener is just another form of a listener pattern.
In order to implement observer pattern between the Fragment(the subject) and the Activity(the observer), the Activity needs to get the reference of the Fragment using the FragmentManager as you suggested. I suggest to use view model and establish observer pattern between view layer and view model layer instead.
RxJava example:
//MainViewModel.kt
class MainViewModel: ViewModel() {
val dialogText = PublishProcessor.create<String>()
fun postNewDialogText(text: String) {
dialogText.onNext(text)
}
}
// Activity
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.dialogText.subscribe {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}.addTo(disposable)
button.setOnClickListener {
TestPopup().show(supportFragmentManager, "TAG")
// usingRxJava(f)
// usingLiveData(f)
}
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
// Dialog Fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Important!! use activity when getting the viewmodel.
val viewModel = ViewModelProviders.of(requireActivity()).get(MainViewModel::class.java)
button_test.setOnClickListener {
viewModel.postNewDialogText(edit_test.text.toString())
dismiss()
}
}

Resources