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
Related
I am making recyclerview in kotlin in android studio. I'm trying to set up an event listener that puts a button in the recyclerview and outputs a toast message. Even if this#[activity name] is entered instead of this in context, a toast message is not displayed. What went wrong?
The error code is as follows.
Unresolved reference: #UpdateActivity
class UpdateActivity : AppCompatActivity() {
private val vBinding by lazy {ActivityUpdateBinding.inflate(layoutInflater)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(vBinding.root)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = arrayListOf<String>("a", "b", "c")
vBinding.uploadedRecyclerView.adapter = recyclerViewAdapter
vBinding.uploadedRecyclerView.layoutManager = LinearLayoutManager(this)
} // onCreate End
class CustomAdapter:RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = arrayListOf<String>()
class Holder(val vBinding:UploadedRecyclerBinding):RecyclerView.ViewHolder(vBinding.root){
fun setData(name:String){
vBinding.name.text = name
vBinding.getBtn.setOnClickListener {
**Toast.makeText(this#UpdateActivity, "test", Toast.LENGTH_SHORT).show()**
// ↑A compilation error occurs here.**
}
}
} // Holder end
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val vBinding = UploadedRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val name = listData[position]
holder.setData(name)
}
override fun getItemCount(): Int {
return listData.size
}
} // CustomAdapter end
} // UpdateActivity end
you could try put context into constructor of adapter when you create it, and use this context to show a toast. something like this:
class CustomAdapter(
private val mContext: Context
):RecyclerView.Adapter<CustomAdapter.Holder>(){
and in holder class:
Toast.makeText(mContext, "test", Toast.LENGTH_SHORT).show()
finally, when you create adapter in activity:
var recyclerViewAdapter = CustomAdapter(this)
Normally, we'll create adapter class in another file, so use this#UpdateActivity in adapter is bad idea
Instead of using the local context in Toast, always use applicationContext when showing it. So, you should replace that creation of Toast message as,
Toast.makeText(applicationContext, "test", Toast.LENGTH_SHORT).show()
I am using a recyclerView to show my listItems in the home fragment. But I have been stuck on how to open a different activity when items are clicked. How can I do for the next steps? Could you please help me to solve the issue? I would appreciate the help.
HomeFragment.kt
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
lateinit var recycle1:RecyclerView
private val list = ArrayList<Locations>()
private val adapter:Adapter = Adapter(list)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var v =inflater.inflate(R.layout.fragment_home, container, false)
recycle1 = v.findViewById(R.id.rv_item)
recycle1.layoutManager = LinearLayoutManager(activity)
list.clear()
testData()
val adapterr = Adapter(list)
recycle1.adapter = adapterr
adapter.notifyDataSetChanged()
recycle1.setHasFixedSize(true)
return v
}
private fun testData(){
list.add(Locations(R.drawable.book_cafe,"The Book Cafe","20 Martin Rd","Cafe"))
list.add(Locations(R.drawable.citysprouts,"City Sprouts","102 Henderson Road","Cafe"))
list.add(Locations(R.drawable.esplanade,"Library#esplande","8 Raffles Ave","Library"))
list.add(Locations(R.drawable.hf,"Library#Harbourfront","1 HarbourFront Walk","Library"))
list.add(Locations(R.drawable.mangawork,"MangaWork","291 Serangoon Rd","Cafe"))
list.add(Locations(R.drawable.orchard,"Library#orchard","277 Orchard Road","Library"))
list.add(Locations(R.drawable.rabbitandfox,"Rabbit&Fox","160 Orchard Rd","Cafe"))
list.add(Locations(R.drawable.sixlettercoffee,"T6 Letter Coffee","259 Tanjong Katong Rd","Cafe"))
}
}
Adapter.kt
class Adapter (val listItem:ArrayList<Locations>) : RecyclerView.Adapter<Adapter.RecycleViewHolder>(){
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecycleViewHolder {
val view:View = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return RecycleViewHolder(view)
}
override fun getItemCount(): Int {
return listItem.size
}
override fun onBindViewHolder(holder: RecycleViewHolder, position: Int) {
val item = listItem[position]
holder.itemImage.setImageResource(item.itemImage)
holder.heading.text = item.headings
holder.detail.text = item.detail
holder.category.text = item.category
}
Locations. kt
data class Locations(var itemImage:Int,var headings :String,var detail :String,var category :String)
You have to take help of an interface to navigate :
First Create an interface , Suppose name it Navigate
interface Navigate {
fun onRecyclerViewItemClicked(location : Location)
}
Then you have to make use of this interface in the adapter as well as the fragment :
In your adapter , you have to do the following :
class Adapter (val listItem:ArrayList<Locations>) : RecyclerView.Adapter<Adapter.RecycleViewHolder>(){
//Declare listener object
var listener: Navigate? = null
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecycleViewHolder {
val view:View = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return RecycleViewHolder(view)
}
override fun getItemCount(): Int {
return listItem.size
}
override fun onBindViewHolder(holder: RecycleViewHolder, position: Int) {
val item = listItem[position]
holder.itemImage.setImageResource(item.itemImage)
holder.heading.text = item.headings
holder.detail.text = item.detail
holder.category.text = item.category
// Now suppose you want to navigate to another fragment / Activity on click of the itemImage , then do the following
holder.itemImage.setOnClickListener{
listener?.onRecyclerViewItemClicked(item)
}
}
Now in your fragment extend the Navigate Class and implement the override the methods :
class HomeFragment : Fragment() , Navigate {
// Rest of your code
override fun onRecyclerViewItemClicked(location : Location){
// The argument location here is the information of the item that is clicked of
// the RecyclerView
// Here write your code to navigate to the next fragment / activity based on what
//you are using NavController / Fragment Manager
}
}
Set a clickListener in init {} block of your view holder
For eg.
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
init {
itemView.setOnClickListener { // start your activity using view context }
}
}
Never set your click listener in onBindViewHolder() method as click listener will set multiple times because this method method is called every time items are bound in recycler view.
Reference official doc
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 ().
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()
}
}
This is my code on android studio using kotlin to capture and show image on main activity, I want that captured image to be displayed on my other activity. After image has been captured, the image will be displayed on the imageView on main Activity, now i want to pass that image to another activity using buttonClassify
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeTensorClassifier()
buttonRecognize.setOnClickListener {
setVisibilityOnCaptured(false)
cameraView.captureImage {
onImageCaptured(it)
}
buttonClassify.setOnClickListener{
val intent = Intent(this, classify::class.java)
startActivity(intent)
}
}
}
private fun onImageCaptured(it: CameraKitImage) {
val bitmap = Bitmap.createScaledBitmap(it.bitmap, INPUT_WIDTH, INPUT_HEIGHT, false)
showCapturedImage(bitmap)
classifier?.let {
try {
showRecognizedResult(it.recognizeImage(bitmap))
} catch (e: java.lang.RuntimeException) {
Log.e(TAG, "Crashing due to classification.closed() before the recognizer finishes!")
}
}
}
private fun showCapturedImage(bitmap: Bitmap?) {
runOnUiThread {
imageCaptured.visibility = View.VISIBLE
imageCaptured.setImageBitmap(bitmap)
}
}
If you store the image in a file, you can just pass the path to the file. If not, you can pass the Bitmap as a Parcelable in the Intent Extras.
private fun showCapturedImage(bitmap: Bitmap?) {
runOnUiThread {
imageCaptured.visibility = View.VISIBLE
imageCaptured.setImageBitmap(bitmap)
val nextActivityIntent = Intent(this, NextActivity::class.java).apply {
putExtra("captured_image", bitmap)
}
startActivity(nextActivityIntent)
}
}
Then in the next Activity you could retreive it like this:
override fun onCreate(savedInstance: Bundle?) {
...
val capturedImage = intent.extras.getParcelable("captured_image") as Bitmap?
/* Use bitmap as you wish */
}
Beware, large bitmaps can throw Exceptions when trying to be passed as Intent Extras, so consider saving the image and passing around the path.