I am using RecycleView in android studio. When data is coming from Firebase it is correct at first. Later, when I add 1 sms to the list of type List(), I get wrong data as a result.
For example:
If List is {1,2,3,4,5},
Add 6 to the list on the screen
The elements of List {1,2,3,4,5,6} should appear in this order.
In this case,
After the chase
List {1,2,3,4,5,1,2,3,4,5,6} is like this. After 1 update it works fine.
class ChatAdapter(var list:MutableList<Sms>,var context: Context):RecyclerView.Adapter<RecyclerView.ViewHolder>(){
companion object {
private const val ITEM_RECEIVE = 1
private const val ITEM_SENT = 2
}
private val shared by lazy {
SharedPref(context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 1) {
val view = LayoutInflater.from(context).inflate(R.layout.item_he_sms, parent, false)
ReceiveVewHolder(view)
}else{
val view = LayoutInflater.from(context).inflate(R.layout.item_my_sms, parent, false)
SentViewHolder(view)
}
}
override fun getItemViewType(position: Int): Int {
val currentMessage = list[position]
return if (currentMessage.emailAddress==shared.getEmail()){
ITEM_SENT
}else{
ITEM_RECEIVE
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (shared.getEmail()==list[position].emailAddress){
holder as SentViewHolder
holder.textView.text = list[position].smsText
}else{
holder as ReceiveVewHolder
holder.textView.text = list[position].smsText
if (list[position].gender=="Male"){
holder.imagePerson.setImageResource(R.drawable.male_avatar)
}else{
holder.imagePerson.setImageResource(R.drawable.female_avatar)
}
}
}
override fun getItemCount(): Int =list.size
inner class SentViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.my_message)
}
inner class ReceiveVewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.he_message)
val imagePerson:CircleImageView=view.findViewById(R.id.imagePerson)
}
}
Create this methode inside your Adapter :
fun updateList(list: List<Sms>){
this.list.clear()
this.list.addAll(list)
notifyDataSetChanged()
}
then call it from inside your observer method, it should resolve your problem.
Xato Firebase'dan getAllSms da, sendSms() usuli ishlayotganda, {1,2,3,4,5,1,2,3,4,5,6} ro'yxat sifatida qaytariladi. result.invoke ni bajarishdan oldin onDataChange-da list.clear ni bajarishingiz kerak.
override fun getAllSms(result: (UiState<ArrayList<Sms>>) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val list = ArrayList<Sms>()
myRef.getReference("global")
.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
list.clear()
snapshot.children.forEach {
val sms:Sms=it.getValue(Sms::class.java)!!
list.add(sms)
}
list.forEach {
Log.d("LLIISSTT", it.smsText)
}
result.invoke(UiState.Success(list))
}
override fun onCancelled(error: DatabaseError) {
result.invoke(UiState.Failure(error.message))
}
})
}
}
Related
I can run this and I also got data appear but I got this warning on my Run terminal
"E/RecyclerView: No adapter attached; skipping layout
No adapter attached; skipping layout"
How can I solve this
class SpacedFragment : Fragment() {
private lateinit var kanjiViewModel : KanjiViewModel
private lateinit var spacedRecyclerAdapter : SpacedRecyclerAdapter
private lateinit var day1Adapter: SpacedRecyclerAdapter
private var _binding : FragmentSpacedBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View {
_binding = FragmentSpacedBinding.inflate(inflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
private fun initUI() {
val dao = KanjiDatabase.getInstance(requireContext()).dao()
val repository = KanjiRepository(dao)
val factory = KanjiViewModelFactory(repository)
kanjiViewModel = ViewModelProvider(this,factory)[KanjiViewModel::class.java]
//adapter
spacedRecyclerAdapter = SpacedRecyclerAdapter()
day1Adapter = SpacedRecyclerAdapter()
//layoutManager
binding.spacedRecyclerview.layoutManager = GridLayoutManager(context,5)
binding.spacedRecyclerviewDay1.layoutManager = GridLayoutManager(context,5)
getData()
}
private fun getData() {
kanjiViewModel.kanjiList.observe(viewLifecycleOwner, { data ->
spacedRecyclerAdapter.submitList(data.filter { it.spacedstatus == 0 })
binding.spacedRecyclerview.adapter = spacedRecyclerAdapter
day1Adapter.submitList(data.filter { it.Japanese_Language_Proficiency_Test == 4 })
binding.spacedRecyclerviewDay1.adapter = day1Adapter
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
There is a sorting error in the relevant lines.
day1Adapter.submitList(data.filter { it.Japanese_Language_Proficiency_Test == 4 })
binding.spacedRecyclerviewDay1.adapter = day1Adapter
You should use submitlist after assigning adapter
set your adapter inside initUI function
private fun initUI() {
...code
spacedRecyclerAdapter = SpacedRecyclerAdapter()
binding.spacedRecyclerview.adapter = spacedRecyclerAdapter
day1Adapter = SpacedRecyclerAdapter()
binding.spacedRecyclerviewDay1.adapter = day1Adapter
...code
getData()
}
private fun getData() {
kanjiViewModel.kanjiList.observe(viewLifecycleOwner, { data ->
spacedRecyclerAdapter.submitList(data.filter { it.spacedstatus == 0 })
day1Adapter.submitList(data.filter { it.Japanese_Language_Proficiency_Test == 4 })
})
}
I have a recyclerview where view type is edittext and on value entered of the edittext, other edittext values should be added with current value.
value entered in edittext item 1 of recyclerview and value entered in edittext item 2 should be the sum in the edittext item 3 which is not editable.
So in below code, the type 'Edittext' is used to identify that these edittext have text watchers and on change of these editttext values, the values of calculations be set in else part.
How to update the data in else code when i am updating the text in editable edittext. this is my code below.
Anyone having solution,
// enter code here
class ProfileDataSubAdapter(
val context: Context,
var repeatedDataList: RealmList<EntityDetails>
) : RecyclerView.Adapter<ProfileDataSubAdapter.DataAdapterViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): DataAdapterViewHolder {
val layout = when (viewType) {
TYPE_EDITTEXT -> R.layout.item_edittext
TYPE_SPINNER -> R.layout.item_spinner
TYPE_CALCULATE -> R.layout.item_edittext
else -> throw IllegalArgumentException("Invalid view type")
}
val view = LayoutInflater
.from(context)
.inflate(layout, parent, false)
return DataAdapterViewHolder(view)
}
override fun onBindViewHolder(holder: DataAdapterViewHolder, position: Int) {
holder.bind(repeatedDataList[position], context, position)
}
fun getRepeatedList(): RealmList<EntityDetails>? {
repeatedDataList.forEach {
if (it.ValueType.equals("EditText") && it.UserEnterValue?.isNullOrEmpty()!!) {
Toast.makeText(context, "Field ${it.EntityName} is empty", Toast.LENGTH_LONG).show()
return null;
}
}
return repeatedDataList
}
override fun getItemCount(): Int = repeatedDataList.size
override fun getItemViewType(position: Int): Int {
if (repeatedDataList[position]?.ValueType == "EditText") {
return TYPE_EDITTEXT
} else if (repeatedDataList[position]?.ValueType == "RadioButton") {
return TYPE_SPINNER
} else
return TYPE_CALCULATE
}
companion object {
private const val TYPE_EDITTEXT = 0
private const val TYPE_SPINNER = 1
private const val TYPE_CALCULATE = 3
}
inner class DataAdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindEdittextData(item: EntityDetails, position: Int) {
//Do your view assignment here from the data model
itemView.findViewById<TextView>(R.id.tv_edit_text_label)?.text = item.EntityName
val content = itemView.findViewById<EditText>(R.id.et_content)
if (item.UserEnterValue?.isNotEmpty()!!) {
content.setText(item.UserEnterValue)
}
//if type is edittext then set text watchers here
if (item.ValueType == "EditText") {
content.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
repeatedDataList[position]?.UserEnterValue = p0.toString()
}
override fun afterTextChanged(p0: Editable?) {
}
})
}
// else if type is calculation set the total here
else {
content.setInputType(InputType.TYPE_NULL)
}
}
fun bindSpinnerData(item: EntityDetails, context: Context) {
//Do your view assignment here from the data model
itemView.findViewById<TextView>(R.id.tv_spinner_label)?.text = item.EntityName
val spinner = itemView.findViewById<Spinner>(R.id.spinner_content)
spinner.adapter = ArrayAdapter<String>(
context,
R.layout.row_spinner,
R.id.txt_content,
item.Values?.map { it.Value } as MutableList<String>
)
// spnr_house.setSelection(getIndexSpinner(spnr_house, noOfYearsInCurrentHouse?.trim()))
spinner.setSelection(item.Values!!.indexOfFirst {
it.IsSelected == true
}, false)
// spinner.setSelection()
spinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(view: AdapterView<*>?) {
}
override fun onItemSelected(
parentView: AdapterView<*>?,
selectedView: View?,
pos: Int,
id: Long
) {
val selectedItem =
spinner.getItemAtPosition(pos)
.toString()
if (selectedItem != "Select") {
item.Values?.find { it.Value == selectedItem }?.IsSelected =
true
val list = item.Values?.filter { it.Value != selectedItem }
list?.forEach { it.IsSelected = false }
}
}
}
}
fun bind(
dataModel: EntityDetails?,
context: Context,
position: Int
) {
if (dataModel?.ValueType == "EditText" || dataModel?.ValueType == "Calculation")
bindEdittextData(dataModel, position)
else if (dataModel?.ValueType == "RadioButton")
bindSpinnerData(dataModel, context)
}
}
}
//end code
Is there a way to combine two adapters that are inside a Callback from Model?
Like, I get data from two sources and I want to display both of them in the same recyclerView
I tried to load the method first then put both adapters on concatAdapter but I got a null from this concatAdapter and then my app crashed
here is my method in Java
private void fetchDataKabupaten(){
progressBar.setVisibility(View.VISIBLE);
token = getIntent().getStringExtra("token");
Call<KabupatenModel> kabupatenModelCall = RetrofitClient.getLoginInterface().getKabupatenData("Bearer "+token);
kabupatenModelCall.enqueue(new Callback<KabupatenModel>() {
#Override
public void onResponse(Call<KabupatenModel> call, Response<KabupatenModel> response) {
kabupatenList = response.body().getKabupaten();
adKabupaten = new KabupatenAdapter(MainActivity.this, kabupatenList);
progressBar.setVisibility(View.GONE);
}
}
private void fetchDataKecamatan(){
progressBar.setVisibility(View.VISIBLE);
token = getIntent().getStringExtra("token");
Call<KecamatanModel> kecamatanModelCall = RetrofitClient.getLoginInterface().getKecamatanData("Bearer " + token);
kecamatanModelCall.enqueue(new Callback<KecamatanModel>() {
#Override
public void onResponse(Call<KecamatanModel> call, Response<KecamatanModel> response) {
kecamatanList = response.body().getKecamatan();
adKecamatan = new KecamatanAdapter(MainActivity.this, kecamatanList);
progressBar.setVisibility(View.GONE);
}
}
I have tried to call both in MainActivity then setAdapter with concatAdapter like this:
fetchDataKabupaten();
fetchDataKecamatan();
concatAdapter = new ConcatAdapter(adKecamatan, adKabupaten);
rV.setAdapter(concatAdapter);
why is it null?
but, when I only set one Adapter inside the response method it works.
you can only use one adapter in recyclerview, but adapter can have multiple view.
for example I have adapter for chat, with right and left view
Adapter Chat Detail
class AdapterChatDetail(val datas: ArrayList<ChatMessage>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var ctx: Context
companion object {
const val LEFT = 0
const val RIGHT = 1
}
inner class ViewHolderLeft(val binding: AdapterMyChatLeftBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: ChatMessage) {
}
}
inner class ViewHolderRight(val binding: AdapterMyChatRightBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: ChatMessage) {
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
ctx = parent.context
return when (viewType) {
LEFT -> ViewHolderLeft(
AdapterMyChatLeftBinding.inflate(
LayoutInflater.from(ctx),
parent,
false
)
)
RIGHT -> ViewHolderRight(
AdapterMyChatRightBinding.inflate(
LayoutInflater.from(ctx),
parent,
false
)
)
else -> ViewHolderLeft(
AdapterMyChatLeftBinding.inflate(
LayoutInflater.from(ctx),
parent,
false
)
)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
LEFT -> (holder as ViewHolderLeft).bind(datas[position])
RIGHT -> (holder as ViewHolderRight).bind(datas[position])
}
}
override fun getItemCount() = datas.size
override fun getItemViewType(position: Int): Int {
val data = datas[position]
return if (data.position == "right") RIGHT else LEFT
}
}
App has bottom navigation menus and fragments. This is the fragment which requires swipe detection similar to Tinder:
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
homeViewModel.text.observe(viewLifecycleOwner, {
textView.text = it
})
root.setOnTouchListener(object : OnSwipeTouchListener(requireContext()) {
override fun onSwipeRight() {
Toast.makeText(requireContext(), "Swiped right!", Toast.LENGTH_SHORT).show()
}
override fun onSwipeLeft() {
Toast.makeText(requireContext(), "Swiped left!", Toast.LENGTH_SHORT).show()
}
})
return root
}
}
The app needs Swipe features like Tinder.
Following is the Listener class:
open class OnSwipeTouchListener(ctx: Context) : OnTouchListener {
val gestureDetector: GestureDetector
companion object {
private val SWIPE_THRESHOLD = 100
private val SWIPE_VELOCITY_THRESHOLD = 100
}
init {
gestureDetector = GestureDetector(ctx, GestureListener())
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
var isTouch = false
if (gestureDetector != null && event != null) {
isTouch = gestureDetector.onTouchEvent(event)
} else {
isTouch = true
}
return isTouch
}
inner class GestureListener : SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
return false
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
var result = false
try {
val diffY = e1?.y?.let { e2?.y?.minus(it) }
val diffX = e1?.x?.let { e2?.x?.minus(it) }
if (diffX != null && diffY != null) {
if (abs(diffX) > abs(diffY)) {
if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight()
} else {
onSwipeLeft()
}
result = true
}
}
} else {
onSwipeRight()
result = true
}
} catch (exception: Exception) {
exception.printStackTrace()
}
return result
}
}
open fun onSwipeRight() {}
open fun onSwipeLeft() {}
open fun onSwipeTop() {}
open fun onSwipeBottom() {}
open fun onSwipeDown() {
}
}
The swipe feature or any sort of touch listeners are not working at all. Why is this happening? I'm new to Kotlin and mobile app development. So any help is very appreciated.
In Android, a gesture is defined as the beginning of ACTION_DOWN and ending with ACTION_UP. If you want your view to receive a gesture, you MUST return true for ACTION_DOWN, otherwise, you will got nothing.
Root cause
override fun onDown(e: MotionEvent): Boolean {
return false
}
Because in this callback, you return false, that means your view does not show interest to any gesture, it explains why all callback such as onFling(), onSwipeRight(), onSwipeLeft() is not called.
Solution
Return true for the ACTION_DOWN event.
override fun onDown(e: MotionEvent): Boolean {
return true
}
Here is a great article about the Android Touch System. Please take a look when you have time.
In the Adapter method. i am able to print list elements in updateUsers(newUsers: List)
that means I am getting dataset in the Adapter, but none of the Adapter methods are getting called.
I have set the layout manager too.
I have breakpoints for All adapter methods onCreateViewHolder, onBindViewHolder. but control is not entering code block.
class MainActivity : AppCompatActivity() {
lateinit var viewModel: ListViewModel
private val usersAdapter = UserListAdapter(arrayListOf(),this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
viewModel.refresh()
usersList.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = usersAdapter
}
usersList.adapter = usersAdapter
observeViewModel()
}
fun observeViewModel() {
viewModel.users.observe(this, Observer {
it?.let {
it.forEach {
Log.d("Each Item", it.toString());
}
loading_view.visibility = View.VISIBLE
usersAdapter.updateUsers(it)
}
})
viewModel.userLoadError.observe(this, Observer {
it?.let {
list_error.visibility = if (it) View.VISIBLE else View.GONE
}
})
viewModel.loading.observe(this, Observer {
it?.let {
loading_view.visibility = if (it) View.VISIBLE else View.GONE
if (it) {
list_error.visibility = View.GONE
usersList.visibility = View.GONE
}
}
})
}
}
class UserListAdapter(private var users: ArrayList<User>, private val context : Context) : RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {
fun updateUsers(newUsers: List<User>) {
newUsers.forEach() {
Log.d("updateUsers", it.firstName)
}
users.clear()
users.addAll(newUsers)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : UserViewHolder{
val layoutInflator = LayoutInflater.from(parent.context)
val view = layoutInflator.inflate(R.layout.item_layout,parent,false)
return UserViewHolder(view)
}
override fun getItemCount():Int {
return users.size
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView = view.imageView
private val userName = view.name
private val userEmail = view.email
fun bind(country: User) {
userName.text = country.firstName + " " + country.lastName
userEmail.text = country.email
imageView.loadImage(country.avatar)
}
}
}
I am not exactly sure why your code is not working, here I have implemented using your code and it's working.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ListViewModel
private val usersAdapter = UserListAdapter(arrayListOf(), this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
viewModel.refresh()
usersList.apply {
layoutManager = LinearLayoutManager(this.context,
RecyclerView.VERTICAL, false)
adapter = usersAdapter
}
// usersList.adapter = usersAdapter // Don't need, you already set the
// adapter above.
observeViewModel()
}
private fun observeViewModel() {
viewModel.users.observe(this, Observer {
it?.let {
it.forEach {
Log.d("Each Item", it.toString());
}
//loading_view.visibility = View.VISIBLE
usersAdapter.updateUsers(it)
}
})
// commented for simplicity
//viewModel.userLoadError.observe(this, Observer {
// it?.let {
//list_error.visibility = if (it) View.VISIBLE else View.GONE
// }
//})
// viewModel.loading.observe(this, Observer {
// it?.let {
// loading_view.visibility = if (it) View.VISIBLE else View.GONE
// if (it) {
// list_error.visibility = View.GONE
// usersList.visibility = View.GONE
// }
//
// }
// })
}
}
UserListAdapter
class UserListAdapter(private var users: ArrayList<User>, private val context : Context) : RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {
fun updateUsers(newUsers: List<User>) {
newUsers.forEach() {
Log.d("updateUsers", it.firstName)
}
users.clear()
users.addAll(newUsers)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : UserViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_layout,parent,false)
return UserViewHolder(view)
}
override fun getItemCount():Int {
return users.size
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView = view.imageView
private val userName = view.name
private val userEmail = view.email
fun bind(country: User) {
userName.text = country.firstName + " " + country.lastName
userEmail.text = country.email
//imageView.loadImage(country.avatar)
}
}
}
ListViewModel
class ListViewModel: ViewModel() {
val users : MutableLiveData<List<User>> = MutableLiveData()
fun refresh() {
val userList = mutableListOf<User>()
userList.add(User(
"User1 First Name",
"User1 last Name",
"example#gmail.com"
))
userList.add(User(
"User2 First Name",
"User2 First Name",
"example#gmail.com"
))
userList.add(User(
"User3 First Name",
"User3 First Name",
"example#gmail.com"
))
userList.add(User(
"User4 First Name",
"User4 First Name",
"example#gmail.com"
))
userList.add(User(
"User5 First Name",
"User5 First Name",
"example#gmail.com"
))
userList.add(User(
"User6 First Name",
"User6 First Name",
"example#gmail.com"
))
users.value = userList
}
}
Hope this helps.