To be as clear as possible, I'm going to show the problem with a gif.
As you can see, the text is overlapping in my emulator and I don't know how to solve it, I don't know what I'm doing wrong.
It should be displayed correctly without overlapping. What I'm doing is subscribing to changes in both LiveData and StateFlow. When I press the button, I make it change the value, but instead of changing, it seems to return both values at once (Default and Hello). Could it be that the view is not refreshing? Or could it be a problem with my emulator?
Anyway, I'll leave all the code here in case you can figure it out.
FlowsFragment.kt
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.rr.stateflow_livedata_flow_sharedflow.databinding.FragmentFlowsBinding
import kotlinx.coroutines.flow.collectLatest
class FlowsFragment : Fragment(R.layout.fragment_flows) {
private lateinit var binding: FragmentFlowsBinding
private val viewModel: FlowsViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentFlowsBinding.bind(view)
binding.btnLiveData.setOnClickListener {
viewModel.triggerLiveData()
}
binding.btnStateFlow.setOnClickListener {
viewModel.triggerStateFlow()
}
subscribeToObservables()
}
private fun subscribeToObservables() {
viewModel.liveData.observe(viewLifecycleOwner) {
binding.tvLiveData.text = it
}
lifecycleScope.launchWhenStarted {
viewModel.stateFlow.collectLatest {
binding.tvStateFlow.text = it
}
}
}
}
FlowsViewModel.kt
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.*
class FlowsViewModel : ViewModel() {
private val _liveData = MutableLiveData("Default LiveData")
val liveData: LiveData<String> = _liveData
private val _stateFlow = MutableStateFlow("Default StateFlow")
val stateFlow: StateFlow<String> = _stateFlow.asStateFlow()
fun triggerLiveData() {
_liveData.value = "Hello LiveData!"
}
fun triggerStateFlow() {
_stateFlow.value = "Hello StateFlow!"
}
}
fragment_flows.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FlowsFragment">
<TextView
android:id="#+id/tvLiveData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-condensed"
android:text="Hello World!"
android:textColor="#color/black"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/btnLiveData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="LIVEDATA"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvLiveData" />
<TextView
android:id="#+id/tvStateFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-condensed"
android:text="Hello World!"
android:textColor="#color/black"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnLiveData" />
<Button
android:id="#+id/btnStateFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="STATEFLOW"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvStateFlow" />
<TextView
android:id="#+id/tvFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-condensed"
android:text="Hello World!"
android:textColor="#color/black"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.504"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnStateFlow" />
<Button
android:id="#+id/btnFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="FLOW"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvFlow" />
<TextView
android:id="#+id/tvSharedFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-condensed"
android:text="Hello World!"
android:textColor="#color/black"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnFlow" />
<Button
android:id="#+id/btnSharedFlow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="SHAREDFLOW"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvSharedFlow" />
</androidx.constraintlayout.widget.ConstraintLayout>
I hope that the text doesn't overlap and returns the latest value of LiveData and StateFlow.
Edit with more info: The application displays the view directly from the fragment, MainActivity only has a fragment container that I show below.
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.add
import androidx.fragment.app.commit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.commit {
setReorderingAllowed(true)
add<FlowsFragment>(R.id.mainActivityFragmentContainer)
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/mainActivityFragmentContainer"
android:name="com.rr.stateflow_livedata_flow_sharedflow.FlowsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="#layout/fragment_flows" />
</androidx.constraintlayout.widget.ConstraintLayout>
EDIT This is not the solution, it's a bad way to solve the problem.
I just have to add this line of code in fragment_flows.xml.
android:background="#color/white"
Just by setting the background of the fragment to white, the text is no longer duplicated and overlapping.
Does anyone know why this happens? Why does what is drawn on the screen stick to the background if there is no background in the fragment layout?
I have a recyclerview with adapter (to show external strings)and I'm trying to click a button "copy" so that it copies the strings in view to the clipboard.
CardLayout.xml Button to use as copy button is android:id="#+id/copyactivity"
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="0dp"
app:cardBackgroundColor="#color/white"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardMaxElevation="3dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true"
tools:context="ui.CardLayout">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="#+id/idActivityName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="14dp"
android:layout_marginRight="10dp"
android:text="#string/placeholdertasktitle"
android:textColor="#color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="#+id/idActivityDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/idActivityName"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="30dp"
android:drawablePadding="2dp"
android:text="#string/let_s_get_started_creating_a_photo_album_for_you_so_you_can_share_with_the_kids_in_the_near_future"
android:textColor="#color/black"
android:textSize="15sp"
tools:ignore="UnknownId" />
<View
android:id="#+id/divider"
android:layout_width="380dp"
android:layout_height="2dp"
android:layout_below="#id/idActivityDescription"
android:layout_alignStart="#id/idActivityName"
android:layout_centerHorizontal="false"
android:layout_centerVertical="false"
android:background="#EDEDED" />
<Button
android:id="#+id/saves"
style="#style/cardbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/divider"
android:layout_alignStart="#id/idActivityName"
android:background="#drawable/cardbutton"
android:contentDescription="#string/saves"
android:stateListAnimator="#null"
android:text="Save"
android:textAlignment="textStart"
android:textColor="#595959"
android:textSize="12sp"
app:icon="#drawable/ic_saves_blank"
app:iconGravity="textStart"
app:iconTint="#595959" />
<Button
android:id="#+id/calendar"
style="#style/cardbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/divider"
android:layout_marginStart="35dp"
android:layout_marginEnd="35dp"
android:layout_toStartOf="#+id/copyactivity"
android:layout_toEndOf="#id/saves"
android:background="#drawable/cardbutton"
android:contentDescription="#string/saves"
android:stateListAnimator="#null"
android:text="#string/calendar"
android:textAlignment="textStart"
android:textColor="#595959"
android:textSize="12sp"
app:icon="#drawable/ic_calendar"
app:iconGravity="textStart"
app:iconTint="#595959" />
<Button
android:id="#+id/copyactivity"
style="#style/cardbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/divider"
android:layout_alignEnd="#id/divider"
android:background="#drawable/cardbutton"
android:contentDescription="#string/saves"
android:stateListAnimator="#null"
android:text="#string/copy"
android:textAlignment="textStart"
android:textColor="#595959"
android:textSize="12sp"
app:icon="#drawable/ic_copytext"
app:iconGravity="textStart"
app:iconTint="#595959" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
And this is my TaskAdapter.kt
package com.example.what2do_v6.Adapter
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService
import androidx.recyclerview.widget.RecyclerView
import com.example.what2do_v6.Model.TaskResponse
import com.example.what2do_v6.R
import com.example.what2do_v6.Repository.TaskRepository
import com.example.what2do_v6.databinding.CardLayoutBinding
import com.example.what2do_v6.ui.CardLayout
import android.content.Context as ContentContext
class TaskAdapter: RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CardLayoutBinding.inflate(layoutInflater, parent, false)
return TaskViewHolder(binding)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
holder.bind(TaskRepository.list[position])
}
override fun getItemCount(): Int {
return TaskRepository.list.size
}
class TaskViewHolder(private val binding: CardLayoutBinding) : RecyclerView.ViewHolder(binding.root){
var bCopyText: Button? = null
fun bind(task: TaskResponse) {
binding.idActivityName.text = task.title
binding.idActivityDescription.text = task.description
bCopyText = findViewById<View>(R.id.copyactivity) as Button
val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
bCopyText!!.setOnClickListener {
val clipData = ClipData.newPlainText("", task.description,)
clipboardManager.setPrimaryClip(clipData)
Toast.makeText(RecyclerView, "message...", Toast.LENGTH_SHORT).show()
}
}
private fun getSystemService(clipboardService: String): Any {
}
}
}
There are two lines in my code that are throwing up problems:
bCopyText = findViewById<View>(R.id.copyactivity) as Button
and
Toast.makeText(RecyclerView, "message...", Toast.LENGTH_SHORT).show()
I've tried different variances of the two above lines and research many articles on StackOverflow but with no winner, can anyone help?
Managed to get this working utilising help from Gobu:
class TaskViewHolder(private val binding: CardLayoutBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(task: TaskResponse) {
binding.idActivityName.text = task.title
binding.idActivityDescription.text = task.description
binding.copyactivity
binding.copyactivity.setOnClickListener {
val clipboardManager = itemView.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText("", task.title +"\n\n" + task.description)
clipboardManager.setPrimaryClip(clipData)
Toast.makeText(itemView.context, "Activity copied to clipboard", Toast.LENGTH_SHORT).show()
}
}
First of all, if you're using ViewBinding, why would you declare a variable for your button? Just get it from the binding.
Second, it's giving you an error, because you're making sure (!!) that the button you declare isn't null, but most likely it is, because you're trying to get the ID of a view you don't have.
Third, RecyclerView is not a context, if you are using ViewBinding, you get the context from binding.root.context
I have faced the problem that my application will keep stopping when clicking search page fragments. I do not understand why this problem will happen? I directly used my other success code in another fragment but in this fragment suddenly appear this problem. Can somebody help me? Thank you for your help!!
My problem error :
java.lang.NullPointerException: Attempt to invoke virtual method
'void androidx.recyclerview.widget.RecyclerView.setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager)'
on a null object reference at com.example.assignment_mad.Search_Page_Fragment.onViewCreated(Search_Page_Fragment.kt:153)
My main frgment ( Search_Page_Fragment ) :
package com.example.assignment_mad
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_notification_.*
class Search_Page_Fragment : Fragment() {
private lateinit var newRecyclerView: RecyclerView
private lateinit var newArrayList: ArrayList<Company_Search>
private lateinit var tempArrayList: ArrayList<Company_Search>
lateinit var imageId:Array<Int>
lateinit var job_name:Array<String>
lateinit var saveicon:Array<Int>
lateinit var company_name:Array<String>
lateinit var place:Array<String>
lateinit var salary:Array<String>
lateinit var news:Array<String>
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<Search_Page_Adapter.MyViewHolder>? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var views =inflater.inflate(R.layout.fragment_search__page_, container, false)
newRecyclerView=views.findViewById(R.id.recyclerView_Search)
// Inflate the layout for this fragment
return views
}
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
imageId= arrayOf(
R.drawable.company_logo_1,
R.drawable.company_logo_2,
R.drawable.company_logo_3,
R.drawable.company_logo_4,
R.drawable.company_logo_5,
R.drawable.company_logo_6,
R.drawable.company_logo_7,
R.drawable.company_logo_8,
R.drawable.company_logo_9,
R.drawable.company_logo_10
)
job_name= arrayOf(
"Engineer",
"Computer",
"Coder",
"Teacher",
"Engineer",
"Engineer",
"Engineer",
"Engineer",
"Engineer",
"Engineer"
)
saveicon= arrayOf(
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24,
R.drawable.ic_baseline_bookmark_added_24
)
company_name= arrayOf(
"Phonix Sdn Bhd",
"Computer Sdn Bhd",
"Coder",
"Teacher",
"Engineer",
"Engineer",
"Engineer",
"Engineer",
"Engineer",
"Engineer"
)
place= arrayOf(
"Kuala Lumpur",
"Kuala Lumpur",
"Selangor",
"Rimbunan",
"Kuala Lumpur",
"Kuala Lumpur",
"Kuala Lumpur",
"Kuala Lumpur",
"Kuala Lumpur",
"Kuala Lumpur"
)
salary= arrayOf(
"RM3000",
"RM4000",
"RM3500",
"RM2000",
"RM1000",
"RM3000",
"RM3000",
"RM3000",
"RM3000",
"RM3000",
)
news= arrayOf(
getString(R.string.news_a),
getString(R.string.news_b),
getString(R.string.news_c),
getString(R.string.news_d),
getString(R.string.news_e),
getString(R.string.news_f),
getString(R.string.news_g),
getString(R.string.news_h),
getString(R.string.news_i),
getString(R.string.news_j)
)
newArrayList= arrayListOf<Company_Search>()
tempArrayList= arrayListOf<Company_Search>()
getUserdata()
super.onViewCreated(itemView, savedInstanceState)
newRecyclerView.apply {
layoutManager= LinearLayoutManager(activity)
newRecyclerView.setHasFixedSize(true)
}
recyclerView.apply {
// set a LinearLayoutManager to handle Android
// RecyclerView behavior
layoutManager= LinearLayoutManager(activity)
// set the custom adapter to the RecyclerView
adapter = Search_Page_Adapter(newArrayList)
}
}
private fun getUserdata() {
for (i in imageId.indices){
val companySearch=Company_Search(imageId[i],job_name[i],saveicon[i],company_name[i],place[i],salary[i])
newArrayList.add(companySearch)
}
// tempArrayList.addAll(newArrayList)
//
// val adapter = Notification_Fragment(tempArrayList)
var adapter=Search_Page_Adapter(newArrayList)
newRecyclerView.adapter=adapter
adapter.setOnItemClickListener(object :Search_Page_Adapter.onItemClickListener{
override fun onItemClick(position: Int) {
Toast.makeText(context,"You clicked in item no . $position",Toast.LENGTH_SHORT).show()
val intent= Intent(context,Search_Page_Detail::class.java)
intent.putExtra("name",newArrayList[position].job_name)
intent.putExtra("imageId",newArrayList[position].titleImage)
intent.putExtra("news",news[position])
startActivity(intent)
}
})
}
}
The page adapter :
package com.example.assignment_mad
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.imageview.ShapeableImageView
class Search_Page_Adapter(private val companysList:ArrayList<Company_Search>):RecyclerView.Adapter<Search_Page_Adapter.MyViewHolder>() {
private var mListener:onItemClickListener?=null
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener=listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView=LayoutInflater.from(parent.context).inflate(R.layout.list_item_search,parent,false)
return MyViewHolder(itemView,mListener)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem=companysList[position]
holder.titleImage.setImageResource(currentItem.titleImage)
holder.job_name.text=currentItem.job_name
holder.save_icon.setImageResource(currentItem.save_icon)
holder.company_name.text=currentItem.company_name
holder.place.text=currentItem.place
holder.salary.text=currentItem.salary
}
override fun getItemCount(): Int {
return companysList.size
}
//to insert the post detail
class MyViewHolder(itemView: View,listener: onItemClickListener?):RecyclerView.ViewHolder(itemView){
val titleImage:ShapeableImageView=itemView.findViewById(R.id.title_image2)
val job_name:TextView=itemView.findViewById(R.id.job_name)
val save_icon:ShapeableImageView=itemView.findViewById(R.id.save_icon)
val company_name:TextView=itemView.findViewById(R.id.company_name)
val place:TextView=itemView.findViewById(R.id.detail_place)
val salary:TextView=itemView.findViewById(R.id.detail_salary)
init {
itemView.setOnClickListener{
listener?.onItemClick(adapterPosition)
}
}
}
}
The xml file for Search_Page_Fragment :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".Search_Page_Fragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search Job"
android:inputType="text"
android:minHeight="48dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="#+id/place_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="Area, City or Town"
android:textSize="16sp"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/search_bar"
app:layout_constraintStart_toStartOf="parent"/>
<Spinner
android:id="#+id/spinner_job"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/place_name" />
<TextView
android:id="#+id/job_specialize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="Job Specialize"
android:textSize="16sp"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/search_bar"
app:layout_constraintEnd_toEndOf="parent" />
<Spinner
android:id="#+id/spinner_place"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
app:layout_constraintTop_toBottomOf="#+id/job_specialize"
app:layout_constraintEnd_toEndOf="parent"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView_Search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="108dp"
app:layout_constraintTop_toBottomOf="#+id/spinner_job"
tools:layout_editor_absoluteX="-16dp"
tools:listitem="#layout/list_item_search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
The list_item_search_xml :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="8dp">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="#+id/title_image2"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="#style/RoundCorner"
android:src="#drawable/company_logo_1"/>
<TextView
android:id="#+id/job_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/blue"
android:text="Gradute Engineer"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_image2"/>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="#+id/save_icon"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_image2"
android:layout_marginEnd="32dp"
app:shapeAppearanceOverlay="#style/RoundCorner"
android:src="#drawable/ic_baseline_bookmark_added_24"/>
<TextView
android:id="#+id/company_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="Neric Compat"
android:textSize="18sp"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/job_name"/>
<TextView
android:id="#+id/detail_place"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="Petaling Jaya"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/company_name"/>
<TextView
android:id="#+id/detail_salary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:text="MYR 3,700 - 4,300 /Month"
android:textSize="16sp"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/detail_place"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
style="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/detail_salary"
android:background="#color/underline"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Your NullPointerException indicates that when this line of code is reached...
layoutManager= LinearLayoutManager(activity)
...that your newRecyclerView is null. Are you sure that you have the right resource ID here when you attempt to set it?
newRecyclerView=views.findViewById(R.id.recyclerView_Search)
Try using the debugger and set a breakpoint on these two lines to confirm is newRecyclerView is indeed null.
You must assign the RecyclerView a LayoutManager. Since you have both of them, place this line as the last statement of recyclerView.apply { … } block(s):
setLayoutManager(layoutManager)
// ↑↑↑↑↑↑↑↑↑↑↑↑↑
// Your LayoutManager
Problem: If the ImageView is VISIBLE and the fragment_list_element_content TextView has only a few lines of text, margins above and below fragment_list_element_text_view_group are too large. If the ImageView is GONE and the fragment_list_element_content TextView has many lines of text, margins above and below fragment_list_element_text_view_group are too small.
xml of my problematic Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/fragment_list_element_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/roundrect_fragment_list_element_background"
android:elevation="#dimen/fragment_list_element_background_elevation"
android:layout_marginTop="#dimen/fragment_list_element_background_margin_top"
android:layout_marginStart="#dimen/fragment_list_element_background_margin_start"
android:layout_marginEnd="#dimen/fragment_list_element_background_margin_end"
android:layout_marginBottom="#dimen/fragment_list_element_background_margin_bottom"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="#+id/fragment_list_element_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/placeholder_image"
android:contentDescription="#string/fragment_list_element_image_view_content_description"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/fragment_list_element_text_view_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/fragment_list_element_textviews_margin_top"
android:layout_marginStart="#dimen/fragment_list_element_textviews_margin_start"
android:layout_marginEnd="#dimen/fragment_list_element_textviews_margin_end"
android:layout_marginBottom="#dimen/fragment_list_element_textviews_margin_bottom"
app:layout_constraintTop_toBottomOf="#id/fragment_list_element_image_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="#+id/fragment_list_element_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/default_date_string"
android:textSize="#dimen/fragment_list_element_date_font_size"
android:textColor="#color/font_color_grey"
android:textAllCaps="true"
android:maxLines="1"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="#id/fragment_list_element_title" />
<TextView
android:id="#+id/fragment_list_element_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/default_text"
android:textSize="#dimen/fragment_list_element_title_font_size"
android:textColor="#color/font_color_black"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="#id/fragment_list_element_date"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="#id/fragment_list_element_content" />
<TextView
android:id="#+id/fragment_list_element_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/default_text"
android:textSize="#dimen/fragment_list_element_content_font_size"
android:textColor="#color/font_color_grey"
app:layout_constraintTop_toBottomOf="#id/fragment_list_element_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
inflation of the layout in my RecyclerViewAdapter:
import android.annotation.SuppressLint
import android.os.Build
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import my.project.R
import java.text.SimpleDateFormat
class MyListAdapter(private val elementList: List<Element>)
: RecyclerView.Adapter<MyListAdapter.ViewHolder>() {
class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
val background: ConstraintLayout = view.findViewById(R.id.news_list_element_background)
val imageView: ImageView = view.findViewById(R.id.news_list_element_image_view)
val date: TextView = view.findViewById(R.id.news_list_element_date)
val title: TextView = view.findViewById(R.id.news_list_element_title)
val content: TextView = view.findViewById(R.id.news_list_element_content)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.fragment_list_element,
parent,
false
)
return ViewHolder(itemView)
}
#SuppressLint("SimpleDateFormat")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val element = elementList[position]
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.background.clipToOutline = true
}
if (element.thumbnail != null) {
holder.imageView.setImageBitmap(element.thumbnail)
holder.imageView.visibility = View.VISIBLE
} else {
holder.imageView.visibility = View.GONE
}
holder.date.text = SimpleDateFormat("dd. MMMM yyyy").format(element.date)
holder.title.text = element.title
holder.content.text = element.content
}
override fun getItemCount(): Int {
return elementList.size
}
}
Having a layout with android:layout_height="match_parent" directly inside an element with android:layout_height="wrap_content" seems to cause this problem.
Why this doesn't create a build error, throws a RuntimeException during inflation or at least shows a Lint warning is a mystery to me.
I got an error when I scroll down the list view. When scroll reached to bottom of activity, the error occurred. And one strange thing is here. On kotlin code, there is "DataController.getDataList()" function. This function get 10 data list as ArrayList, but I got only 9 data in my emulator. What's wrong with my code?
activity_spiral_test_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".spiralTestListActivity"
android:background="#color/Background">
<ListView
android:id="#+id/patientList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewStub
android:id="#+id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout="#layout/empty"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="80dp"
android:layout_height="80dp"
android:src="#drawable/add"
android:layout_margin="40dp"
android:scaleType="center"
android:tint="#android:color/white"
android:backgroundTint="#color/Primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:borderWidth="0dp"/>
</android.support.constraint.ConstraintLayout>
patient_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:orientation="vertical">
<TextView
android:id="#+id/patientName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Patient"
android:textSize="40sp"
android:textColor="#android:color/black"
android:textStyle="bold"/>
<TextView
android:id="#+id/patientDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Explanation"
android:textSize="25sp"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginEnd="40dp"
android:orientation="vertical">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:src="#drawable/assignment"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test"
android:textSize="20sp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
spiralTestListActivity.kt
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import myPackage.DataController
import myPackage.PatientData
import kotlinx.android.synthetic.main.activity_spiral_test_list.*
class spiralTestListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_spiral_test_list)
patientList.adapter = patientAdapter(this, DataController.getDataList())
patientList.emptyView = empty
}
private inner class ViewHolder {
lateinit var name: TextView
lateinit var description: TextView
}
inner class patientAdapter(context: Context, val listItem: ArrayList<PatientData>) : BaseAdapter() {
private val mInflator: LayoutInflater = LayoutInflater.from(context)
override fun getCount(): Int {
return this.listItem.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItem(position: Int): Any {
return this.listItem[position]
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View
val holder: ViewHolder
if (convertView == null) {
view = mInflator.inflate(R.layout.patient_list_item, parent, false)
holder = ViewHolder()
holder.name = view.findViewById(R.id.patientName) as TextView
holder.description = view.findViewById(R.id.patientDescription) as TextView
}
else {
view = convertView
holder = convertView.tag as ViewHolder
}
val patient = getItem(position) as PatientData
val name = holder.name
val description = holder.description
name.text = patient.name
description.text = patient.description
return view
}
}
}
I solve this problem. This problem is occurred because of thread. Surely, controller of data is adapter. But, I add data in my dialog class.(There are some different part of code above. I changed it..) So, when thread ask to get data from adapter, the data is different with ListView's data. It occurred an error.
I fix the code in my adapter class and dialog class. In the adapter class, I add a function named add.
fun add(data: PatientData) {
DataController.addData(data)
}
Also, there is one more thing. In SpiralTestListActivity.ky, I don't add
convertView.tag = holder
above the else part in Adapter class..!!
These parts make my app work correctly..!