I'm trying to implement an empty state recycler view based on this post. I have migrated the solution to Kotlin, but the problem is I'm not able to extend CustomRecyclerView.Adapter (Adapter is an abstract class defined in RecyclerView) from the newly defined custom recycler view in Kotlin. And I have observed the same CustomRecyclerView.Adapter can be extended in Java.
Custome RecyclerView implementation
open class CustomRecyclerView: RecyclerView {
private var emptyStateView : View? = null
constructor(context: Context) : super(context)
constructor(context: Context , attrs: AttributeSet) : super(context,attrs)
constructor(context: Context , attrs: AttributeSet, defstyle: Int) : super(context,attrs,defstyle)
var observer: AdapterDataObserver = object : AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
initEmptyView()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
initEmptyView()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
initEmptyView()
}
}
private fun initEmptyView() {
emptyStateView?.let {
it.visibility = if (adapter == null || adapter!!.itemCount == 0) View.VISIBLE else View.GONE
this#CustomRecyclerView.visibility = if (adapter == null || adapter!!.itemCount == 0) View.GONE else View.VISIBLE
}
}
override fun setAdapter(adapter: Adapter<*>?) {
val oldAdapter = getAdapter()
super.setAdapter(adapter)
oldAdapter?.unregisterAdapterDataObserver(observer)
adapter?.registerAdapterDataObserver(observer)
}
/**
* #param emptyView is the view which is going to display when the recycler view is empty
* **/
fun setEmptyView(emptyView: View) {
this.emptyStateView = emptyView
initEmptyView()
}}
Adding images for the extension implementation in java and kotin
To summarise the comments: inherit from RecyclerView.Adapter, see below
YourAdapter: RecyclerView.Adapter<YourViewHolder>()
And just a hint, here's the Kotlin way to extend a class with multiple constructors:
class CustomRecyclerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr)
For more info, check #JvmOverloads
It should be RecyclerView.Adapter.. and set adapter to customRecyclerView.
Related
When i click on multiple images of recyclerview then multiple audios start playing without stopping the previous one. It is getting irritating. I hope someone can help me fix this. I have read about singleton class and using only one instance of media player but couldn't understood how to implement it correctly.
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
var mediaPlayer: MediaPlayer? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
mediaPlayer!!.isLooping = true
holder.soundImage.setOnClickListener {
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}
Code seems to be good. Remember to call release() on your player and initialise it again with new media file before playing it. Maybe it could be useful for you to implement a MediaPlayer.OnCompletionListener in which you could call player.release()
Or maybe this is enough:
holder.soundImage.setOnClickListener {
mediaPlayer!!.release()
mediaPlayer = null
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
I'll recommend to initialize just single object of MediaPlayer in your fragment or activity in which your RecyclerView is located and than pass that object into adapter as a parameter and use it and dont forget to release MediaPlayer in onDestroy() of activity or onDestroyView() of fragment by calling MediaPlayer?.release(). In adapter OnClickListener() you have to stop the media player calling MediaPlayer?.stop() and MediaPlayer?.reset() and than add the new source
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>, var mediaPlayer : MediaPlayer?) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
holder.soundImage.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer?.reset()
mediaPlayer?.setDataSource(itemModel.soundTrack)
mediaPlayer?.isLooping = true
mediaPlayer?.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}
I am new to Firebase and RecyclerViews and I am trying to get a list of ingredients from my Firebase Database and I want to create checkboxes for every item I retrieve from the database. I got as far as creating a RecyclerView and adapter for my Firebase Query but binding lists from the query doesn't seem to be covered anywhere I could find.
Please find below the code for putting the data into the recycler view as well as the layout files being used for the recycler view.
Data being retrieved
that I want to display the original field of my object as checkboxes
ShoppingListFragment.kt
class ShoppingListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Enable Firestore logging
FirebaseFirestore.setLoggingEnabled(true)
//Firestore
firestore = Firebase.firestore
// Get recipes that the user has liked up to ${LIMIT}
query = firestore.collection("recipes").whereArrayContains(
"plannedBy",
Firebase.auth.uid.toString()
)
.orderBy("name", Query.Direction.ASCENDING)
.limit(LIMIT.toLong())
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
...
}
...
}
ShoppingListAdapter.kt
open class ShoppingListAdapter(query: Query) :
FirestoreAdapter<ShoppingListViewHolder>(query) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShoppingListViewHolder {
return ShoppingListViewHolder(
ItemShoppingListBinding.inflate(
LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ShoppingListViewHolder, position: Int) {
holder.bind(getSnapshot(position))
}
}
ShoppingListViewHolder.kt
class ShoppingListViewHolder(val binding: ItemShoppingListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox() //Not sure what to put here as the context for my recyclerview List Item
checkBox.text = recipe.ingredients[i].original
checkBox.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
binding.shoppingListContainer.addView(checkBox)
}
} else {
binding.shoppingListItem.text = recipe.ingredients[0].original
}
}
}
I have tried passing a context parameter through ShoppingListViewHolder.kt and in turn adding that parameter to the adapter and so forth like so.
class ShoppingListViewHolder(val binding: ItemShoppingListBinding, val context: Context) : RecyclerView.ViewHolder(binding.root) {
fun bind(snapshot: DocumentSnapshot) {
val recipe = snapshot.toObject<Recipe>() ?: return
binding.listTitle.text = recipe.name
if(recipe.ingredients.size > 1) {
binding.shoppingListItem.text = recipe.ingredients[0].original
for(i in 1 until recipe.ingredients.size) {
val checkBox = CheckBox(context)
...
}
ShoppingListFragment.kt
// Init the adapter to hold the recipe objects.
adapter = object : ShoppingListAdapter(query, requireContext()) {
override fun onDataChanged() {
if (itemCount == 0) {
shoppingListRecycler.visibility = View.GONE
viewEmpty.visibility = View.VISIBLE
} else {
shoppingListRecycler.visibility = View.VISIBLE
viewEmpty.visibility = View.GONE
}
}
override fun onError(e: FirebaseFirestoreException) {
// Show a snackbar on errors
view?.let {
Snackbar.make(
it,
"Error: check logs for info.", Snackbar.LENGTH_LONG
).show()
}
}
}
But all that did was create a huge amount of white space between entries. After some debugging, it looks like passing the context from the fragment gave it the MainActivity context. I'm not too sure where to go from here and any help would be appreciated. Thank you!
you can use binding.root.context except passing context
I tried to send data from adapter A to Activity A.
In adapter A I used Intent.putExtra() and it's success.
And in Activity A I used getStringExtra() to get the data.
When I clicked an item , the getStringExtra() returns null value. But, when I don't close my app and click the same item again, I got the value.
This is Adapter A :
class AdapterA (private val daftarMeja: ArrayList<DaftarMeja.Meja>, private val clickListener: (DaftarMeja.Meja) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
companion object {
const val ID_MEJA = "idMeja"
const val STATUS_MEJA = "statusMeja"
}
override fun getItemCount(): Int {
return daftarMeja.size
}
fun updateData(dataBaru: ArrayList<DaftarMeja.Meja>) {
daftarMeja.clear()
daftarMeja.addAll(dataBaru)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.item_meja,parent,false)
return PartViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as PartViewHolder).bind(daftarMeja[position], clickListener)
}
inner class PartViewHolder (itemView: View): RecyclerView.ViewHolder(itemView){
fun bind(meja: DaftarMeja.Meja, clicklistener: (DaftarMeja.Meja) -> Unit){
itemView.tv_table_name.text = meja.nama
if(meja.status){
itemView.container_table.setBackgroundResource(R.color.colorTableAvailable)
itemView.tv_table_name.setTextColor(Color.BLACK)
}
else {
itemView.container_table.setBackgroundResource(R.drawable.gradient)
itemView.tv_table_name.setTextColor(Color.WHITE)
}
itemView.setOnClickListener {
val intent = Intent(itemView.context, ActivityA::class.java)
intent.putExtra(ID_MEJA, meja.id)
intent.putExtra(STATUS_MEJA, meja.status)
itemView.context.startActivity(intent)
clicklistener(meja)
}
}
}
}
To get the data in Activity A :
val idMeja = intent.getStringExtra(MejaAdapter.ID_MEJA)
When the item clicked :
private fun mejaItemClicked() {
val intent = Intent(this, ActivityB::class.java)
startActivity(intent)
}
I don't know where is the problem. Please help me to solve this
1)In Activity onCreate() , this is the right mode to test Bundle
Bundle extras = getIntent().getExtras();
if (extras != null)
{
if (extras.containsKey("YOURNAMEPARAM"))
{....your assign}
}
2)Wend lanch a intent FIRST you put in extra of intent the value
val intent = Intent(this, ActivityB::class.java)
intent.putExtra("YOURNAMEPARAM",yourvalue);
startActivity(intent)
3)Make sure that the type of variable you are passing is consistent and use the "PUT" and the "GET"corresponding to the variable.
4)Make sure the activity is not already open, otherwise the onCreate () method will not be called and your getextra will not be re-evaluated.
I have a Coordinator Layout like so:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator_subreddit_selection"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.clans.fab.FloatingActionMenu
android:id="#+id/addFabMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"/>
</android.support.design.widget.CoordinatorLayout>
And in my code, I have a snackbar like so:
Snackbar.make(getView().findViewById(R.id.addFabMenu),
R.string.no_entered_subreddit_name, Snackbar.LENGTH_SHORT).show();
Here is what I get:
And here is what I what:
I've tried copying the code from the example activity exactly but it still does not work like the example activity.
Fixed it by creating a Coordinator behaviour like so:
public class MoveUpwardBehavior extends CoordinatorLayout.Behavior<View> {
private static final boolean SNACKBAR_BEHAVIOR_ENABLED;
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
static {
SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
}
}
And extended my view class, applying the behaviour to it
#CoordinatorLayout.DefaultBehavior(MoveUpwardBehavior.class)
public class MoveUpwardsFloatingMenu extends FloatingActionMenu {
public MoveUpwardsFloatingMenu(Context context) {
super(context);
}
public MoveUpwardsFloatingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MoveUpwardsFloatingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
Try using the standard FloatingActionButton from the support library
Instead of:
<com.github.clans.fab.FloatingActionMenu
android:id="#+id/addFabMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"/>
Use:
<android.support.design.widget.FloatingActionButton
android:id="#+id/addFabMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"/>
CoordinatorLayout.DefaultBehavior annotation was deprecated from API
level 27.1.0.
To add a Behavior to your custom View, you just need to implement CoordinatorLayout.AttachedBehavior interface to return the default behavior or using layout_behavior attribute that is present will override the AttachedBehavior.
Here you can see an example:
class MoveUpwardBehavior : CoordinatorLayout.Behavior<View>() {
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: View,
dependency: View): Boolean = dependency is SnackbarLayout
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: View,
dependency: View
): Boolean {
child.translationY = min(DEFAULT_CHILD_SIZE, dependency.translationY - dependency.height)
return true
}
override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) {
super.onDependentViewRemoved(parent, child, dependency)
child.translationY = DEFAULT_CHILD_SIZE
}
companion object {
private const val DEFAULT_CHILD_SIZE = 0f
}
}
class ExampleComponentView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), CoordinatorLayout.AttachedBehavior {
.....
override fun getBehavior(): CoordinatorLayout.Behavior<*> =
MoveUpwardBehavior()
}
I have a custom CursorAdapter which sets a handler for OnCheckedChangeListener inside bindView (the layout in newView implements the Checkable interface). '=>' is the anonymous function syntax in scala:
def setTaskCheckboxToggleListener() = {
val v = view.findViewById(R.id.taskCheckbox).asInstanceOf[CheckBox]
v.setOnCheckedChangeListener(
(buttonView: CompoundButton, isChecked: Boolean) => {
handler(buttonView, isChecked)
}
)
}
The adapter is used with a listView. Now the handler is set once in the activity containing the listView using:
listView.setAdapter(Tasks.adapter(context))
listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
listView.getAdapter().registerCheckBoxStateChangeHandler((buttonView: CompoundButton, _) => {
findViewById(R.id.commandButton).astInstanceOf[Button].setText("✓")
}
This handler for some reason prevents the checkbox from being toggled. In comparison, the checkbox just works with an empty handler or a handler that doesn't call .setText on the UI element. What could be the problem here?
Checkable layout used in the Adapter, the xml file in res/ has a CheckBox element:
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.{CheckBox, Checkable, CheckedTextView, RelativeLayout}
class TaskLayout(context: Context, attrs: AttributeSet)
extends RelativeLayout(context, attrs) with Checkable {
private var checkbox: CheckBox = _
override def onFinishInflate(): Unit = {
super.onFinishInflate();
for (i <- 0 to getChildCount()) {
val v = getChildAt(i)
if (v.isInstanceOf[CheckBox])
checkbox = v.asInstanceOf[CheckBox];
}
}
override def isChecked(): Boolean = {
if (checkbox != null)
checkbox.isChecked()
else
false
}
override def setChecked(checked: Boolean) =
if (checkbox != null) checkbox.setChecked(checked)
override def toggle() =
if (checkbox != null) checkbox.toggle();
}
Commenting out listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE) solved the problem.