list_users.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/user_list_bg"
android:layout_margin="10dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/user_profile_image_view"
android:layout_width="90sp"
android:layout_height="90sp"
android:src="#drawable/user_image_placeholder"
android:layout_margin="12dp"
app:border_width="1sp"
android:clickable="true"
android:focusable="true">
</de.hdodenhof.circleimageview.CircleImageView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="#+id/user_name_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hardik Dhuri"
android:layout_marginBottom="10dp"
android:textSize="24sp"/>
<TextView
android:id="#+id/last_message_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Bro"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
fragment_chats.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.fragments.ChatsFragment"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list_users_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/list_users"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</FrameLayout>
ListUsersAdapter.kt
package com.example.samvach.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.samvach.R
import com.example.samvach.models.User
class ListUsersAdapter(private val userList: ArrayList<User>) : RecyclerView.Adapter<ListUsersAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_users, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = userList[position]
holder.name.text = item.name
}
override fun getItemCount(): Int {
return userList.size
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val name: TextView = itemView.findViewById(R.id.user_name_tv)
}
}
ChatsFragment.kt
Here I am getting data from firebase. I checked the data by logging it and it seems fine only thing i can't figure out is where is the actual error in my recycler view. I create few users manually to test and they don't show up either.
package com.example.samvach.main.fragments
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.example.samvach.R
import com.example.samvach.adapters.ListUsersAdapter
import com.example.samvach.databinding.FragmentChatsBinding
import com.example.samvach.models.User
import com.google.firebase.database.*
class ChatsFragment : Fragment() {
private lateinit var binding: FragmentChatsBinding
private lateinit var dbref: DatabaseReference
private lateinit var usersArrayList: ArrayList<User>
companion object {
fun newInstance() = ChatsFragment()
}
private lateinit var viewModel: ChatsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_chats, container, false)
}
#Deprecated("Deprecated in Java")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this)[ChatsViewModel::class.java]
binding = FragmentChatsBinding.inflate(layoutInflater)
binding.listUsersRv.setHasFixedSize(true)
usersArrayList = arrayListOf(
User(
name = "Banana"
),
User(
name = "Apple"
),
User(
name = "Kiwi"
),
User(
name = "Orange"
)
)
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
// getUserData()
}
private fun getUserData() {
dbref = FirebaseDatabase.getInstance().getReference("users")
dbref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
usersArrayList.clear()
if (snapshot.exists()) {
for (userSnapshot in snapshot.children) {
val user = userSnapshot.getValue(User::class.java)
Log.i("USER", "${user!!.name}")
usersArrayList.add(user)
}
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
User.kt
package com.example.samvach.models
data class User (
var email: String?= null,
var name: String?= null,
var profilePicture: String?= null,
var uid: String?= null,
)
You have an error in binding = FragmentChatsBinding.inflate(layoutInflater), try to init your binding in onCreateView like this:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_chats, container, false)
binding = FragmentChatsBinding.bind(view)
return binding.root
}
or move your code of onActivityCreated to onViewCreated and use view instance
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_chats, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentChatsBinding.bind(view)
viewModel = ViewModelProvider(this)[ChatsViewModel::class.java]
binding.listUsersRv.setHasFixedSize(true)
usersArrayList = arrayListOf(
User(
name = "Banana"
),
User(
name = "Apple"
),
User(
name = "Kiwi"
),
User(
name = "Orange"
)
)
binding.listUsersRv.adapter = ListUsersAdapter(usersArrayList)
// getUserData()
}
Related
I am trying to develope an app which lets the user choose and read a csv file from his phone, so the app can set a variable with the chosen file content (string). This variable can then be used to set the text of a TextView.
I already was able to let the user of my app choose the desired file. I couldn't get the file content though. And I've already seen that onActivityResult and startActivityForResult are depreacated which means that a lot of tutorials are outdated. Plus I was not able to get a solution to my problem by the documentation here.
This picture shows you, that after the user chooses his csv file, the textView is not changing.
This is what I have come up with so far for my ContentFragment.kt file. As you can see I already have a line for setting the TextView text to the content of a csv file which is located in the asset folder. That works great (here it is commented out - only there fyi), but I would like to do the same for the user's csv file:
package com.example.myapplication
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 com.example.myapplication.databinding.FragmentContentBinding
import kotlinx.android.synthetic.main.fragment_content.*
import java.io.BufferedReader
import java.io.InputStreamReader
class ContentFragment : Fragment() {
var _binding: FragmentContentBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentContentBinding.inflate(inflater, container, false)
val file = BufferedReader(InputStreamReader(resources.assets.open("Testfile.csv"))).use {
it.readText()
} // The variable is unused. This line of code only exists to demonstrate that the file_content.settext(file) in the TestButton click event is working.
binding.TestButton.setOnClickListener {
// file_content.setText(file) // <-- This line works
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
startActivity(Intent.createChooser(intent, "Select a file"))
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?) {
resultData?.data?.also { uri ->
val file = BufferedReader(InputStreamReader(resources.assets.open(resultData.toString()))).use {
it.readText()
}
file_content.setText(file)
}
}
}
This is the xml of the fragment:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ContentFragment"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/TestButton"
android:text="Import File"
tools:ignore="MissingConstraints"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="In here the content of the file will appear."
android:id="#+id/file_content"
tools:ignore="MissingConstraints" />
</LinearLayout>
And here are more files, which are not so necessary:
MainActivity.kt:
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
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"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="56dp"
app:layout_constraintBottom_toBottomOf="parent">
<fragment
android:id="#+id/fragment"
class="com.example.myapplication.ContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Replace:
val file = BufferedReader(InputStreamReader(resources.assets.open(resultData.toString()))).use {
it.readText()
}
with:
val file = BufferedReader(InputStreamReader(requireContext().contentResolver.openInputStream(uri))).use {
it.readText()
}
Note that your use of */* for the MIME type means that the user can choose non-text content. Consider using text/*.
I was able to solve my problem by editing ContentFragment.kt. Now it works to let the user choose a csv-file and show its content in a textView.
package com.example.myapplication
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 androidx.activity.result.contract.ActivityResultContracts
import com.example.myapplication.databinding.FragmentContentBinding
import kotlinx.android.synthetic.main.fragment_content.*
import java.io.BufferedReader
import java.io.InputStreamReader
class ContentFragment : Fragment() {
var _binding: FragmentContentBinding? = null
val binding get() = _binding!!
private val request = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri != null) {
val inputStream = requireContext().contentResolver.openInputStream(uri)
val file = BufferedReader(InputStreamReader(inputStream)).use {
it.readText()
}
file_content.setText(file)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentContentBinding.inflate(inflater, container, false)
binding.TestButton.setOnClickListener {
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
request.launch("*/*")
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
I'm trying to list song files on the device using RecycleView and get the following error:
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
fragments.LoadFragment.loadTrackData(LoadFragment.kt:50)
fragments.LoadFragment.onCreate(LoadFragment.kt:36)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2949)
at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:278)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Line 50 is recycler_view.layoutManager = layoutManager
and line 36 is loadTrackData() calling the function that line 50 is in.
These lines are in the fragment I'm working in:
import android.Manifest
import android.content.pm.PackageManager
import android.database.Cursor
import android.os.Bundle
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.repea.Adapters.TrackListAdapter
import com.example.repea.R
import com.example.repea.TrackData.TrackData
import kotlinx.android.synthetic.main.fragment_load.*
class LoadFragment : Fragment() {
var trackData:ArrayList<TrackData> = ArrayList()
var trackListAdapter:TrackListAdapter?=null
companion object{
val PERMISSION_REQUEST_CODE = 12
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (context?.let { ContextCompat.checkSelfPermission(it.applicationContext,Manifest.permission.READ_EXTERNAL_STORAGE) } !=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this.requireActivity(),
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE)
}else{
loadTrackData()
}
}
fun loadTrackData(){
var trackCursor:Cursor? = activity?.applicationContext?.contentResolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,null,null,null)
while (trackCursor!=null && trackCursor.moveToNext()){
var trackTitle = trackCursor.getString(trackCursor.getColumnIndex(MediaStore.Audio.Media.TITLE))
var trackLength = trackCursor.getString(trackCursor.getColumnIndex(MediaStore.Audio.Media.DURATION))
trackData.add(TrackData(trackTitle,trackLength))
}
trackListAdapter = TrackListAdapter(trackData)
var layoutManager = LinearLayoutManager(activity?.applicationContext)
recycler_view.layoutManager = layoutManager
recycler_view.adapter = trackListAdapter
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_REQUEST_CODE){
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
Toast.makeText(context?.applicationContext,"Permission Granted",Toast.LENGTH_SHORT).show()
loadTrackData()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_load, container, false)
}
}
Here's the adapter:
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.recyclerview.widget.RecyclerView
import com.example.repea.R
import com.example.repea.TrackData.TrackData
class TrackListAdapter(TrackData:ArrayList<TrackData>):RecyclerView.Adapter<TrackListAdapter.TrackListViewHolder>() {
val dtrackData = TrackData
class TrackListViewHolder(itemView: ActionMenuItemView):RecyclerView.ViewHolder(itemView){
var titleTV:TextView
var lengthTV:TextView
var artTV:ImageView
init {
titleTV = itemView.findViewById(R.id.track_title_view)
lengthTV = itemView.findViewById(R.id.track_length_view)
artTV = itemView.findViewById(R.id.track_art_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackListViewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.load_item,parent,false)
return TrackListViewHolder(view as ActionMenuItemView)
}
override fun onBindViewHolder(holder: TrackListViewHolder, position: Int) {
var data = dtrackData[position]
var trackName = data.dTrackTitle
var trackLength = data.dTrackLength
holder.titleTV.text = trackName
holder.lengthTV.text = trackLength
}
override fun getItemCount(): Int {
return dtrackData.size
}
}
And the widget:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.LoadFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
android:clipToPadding="false"/>
</RelativeLayout>
What's gone wrong here?
You are not bind recycler_view with any view so it gives null, you should reference any view after inflate the layout that hold views.
here an example of using RecyclerView into Fragment class
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_blank, container, false)
view.recyclerView.layoutManager = LinearLayoutManager(activity)
view.recyclerView.adapter = MainAdapter()
return view
}
The error message saying that your recycler view is null and that because you used it before it created, so you should move the initialization to the onCreateView method
my thouts:
if (context?.let { ContextCompat.checkSelfPermission(it.applicationContext,Manifest.permission.READ_EXTERNAL_STORAGE) } !=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this.requireActivity(),
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE)
}else{
loadTrackData()
}
Try to muve this in onViewCreated()
Because your views inst loaded yet, when onCreate() is called
And i dint found where are you initialize recycler_view?
I wanted to implement searchview inside bottomsheetdialog fragment but i don't know how. I have this full screen bottom sheet dialog layout with a SearchView and Recyclerview in it:
<?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"
android:background="#color/white">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:layout_marginEnd="19dp"
android:src="#drawable/ic_close_green"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tv_institution_name"
style="#style/bo_texto_regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="21dp"
android:layout_marginTop="66dp"
android:letterSpacing="0.01"
android:text="Nome"
android:textColor="#757575"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.SearchView
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginStart="21dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="27dp"
app:layout_constraintEnd_toEndOf="#id/tv_institution_name"
app:layout_constraintStart_toStartOf="#id/tv_institution_name"
app:layout_constraintTop_toBottomOf="#id/tv_institution_name"
app:searchIcon="#drawable/magnifying_glass" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="#ececec"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/searchView"
tools:listitem="#layout/search_item_list" />
</androidx.constraintlayout.widget.ConstraintLayout>
And this is my full screen BottomSheetDialogFragment :
import android.view.ViewGroup
import android.view.WindowManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koin.android.ext.android.inject
class SearchDialog() : BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(requireContext(), theme)
dialog.setOnShowListener {
val bottomSheetDialog = it as BottomSheetDialog
val parentLayout =
bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
parentLayout?.let { it ->
val behaviour = BottomSheetBehavior.from(it)
setupFullHeight(it)
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
}
}
return dialog
}
private fun setupFullHeight(bottomSheet: View) {
val layoutParams = bottomSheet.layoutParams
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
bottomSheet.layoutParams = layoutParams
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var view = inflater.inflate(R.layout.dialog_search, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
I wanted to make the data come from an array and that i could search it using searchview
My adapter :
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import androidx.recyclerview.widget.RecyclerView
class SearchAdapter(private val list: List<InstitutionModel>) : RecyclerView.Adapter<SearchViewHolder>() , Filterable{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val inflater = LayoutInflater.from(parent.context)
return SearchViewHolder(inflater, parent)
}
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
val dialogData: InstitutionModel = list[position]
holder.bind(dialogData)
}
override fun getFilter(): Filter {
TODO("Not yet implemented")
}
}
And this is the setOnQueryListener:
searchViewInstitutions.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
// searchView.clearFocus()
// if (data.toString().contains(query)) {
// mAdapter.filter.filter(query)
// }
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
mAdapter.filter.filter(newText)
return false
}
})
}
I have a plain basic recycler view and its adapter.In the single recycler view row, I got a textView which are floating numbers.The textView width length shall fixed in proportional sizes, i.e. 30 % of the screen.I want to put OnGlobalLayoutListener to auto resize text downward passively whenever the text exceed over the separator lineso that it fits in the textView box prepared.I don't want WRAP the numbers
So I add OnGlobalLayoutListener on each ViewHolder and expect to detect & tracing each row items if the costTextView is Overlapped to the separator line.
However what happened is it does not resize all rows, but part of items that exposed on user screen only. Example 4-7 does not affected, 8-12 is affected, so on.
MainFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.elliot.financetracker.databinding.MainFragmentBinding
class MainFragment : Fragment() {
companion object {
private val TAG = "HomeFragment"
}
private lateinit var thisView: View
private lateinit var binding: MainFragmentBinding
private var cashList: ArrayList<Cash> = ArrayList()
private lateinit var recyclerView: RecyclerView
private lateinit var mAdapter: CashAdapter
init {
cashList = prepareMovieData()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding =
DataBindingUtil.inflate<MainFragmentBinding>(
inflater,
R.layout.main_fragment, container, false
)
thisView = binding.root
return thisView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = binding.rvCash
mAdapter = CashAdapter(cashList, action)
val mLayoutManager: RecyclerView.LayoutManager = LinearLayoutManager(thisView.context)
recyclerView.layoutManager = mLayoutManager
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = mAdapter
}
private val action = object : CashAdapter.ClickedListener {
override fun onItemClick(cash: Cash) {
val bundle = bundleOf("cash" to cash)
this#MainFragment.findNavController().navigate(R.id.edit_frag, bundle)
}
}
private val actionAddNewRecord = object : View.OnClickListener {
override fun onClick(v: View?) {
val arg = Bundle()
arg.putInt("sss", 43)
this#MainFragment.findNavController().navigate(R.id.edit_frag, arg)
}
}
private fun prepareMovieData(): ArrayList<Cash> {
for (i in 0..29) {
cashList.add(
Cash(
"-2000000000000000000000000.00",
"Action & Adventure $i"
)
)
}
return cashList
}
}
CashAdapter.kt
import android.graphics.Rect
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.elliot.financetracker.CashAdapter.CashViewHolder
import com.elliot.financetracker.databinding.CashListRowBinding
import com.elliot.financetracker.utils.*
//import com.example.recyclerviewbinding.databinding.MovieListRowBinding;
class CashAdapter(
private val cashList: List<Cash>,
private val clickedAction: ClickedListener
) : RecyclerView.Adapter<CashViewHolder>() {
companion object {
private var TAG = "CashAdapter"
}
inner class CashViewHolder(private val binding: CashListRowBinding) :
RecyclerView.ViewHolder(binding.root) {
var positionss: Int = -1
fun bind(cash: Cash, clickedAction: ClickedListener, positionss: Int) {
this.positionss = positionss
binding.cash = cash
binding.cvCashView.setOnClickListener {
clickedAction.onItemClick(binding.cash!!)
}
// ---------- NOTE HERE I ADD GLOBAL LAYOUT LISTENER ---------------------------------------------
this.binding.cvCashView.viewTreeObserver.addOnGlobalLayoutListener(globalObserver)
binding.executePendingBindings()
}
private val globalObserver = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val overlapped = isOverlapped(binding.tvItemCost, binding.separatorCost)
if (overlapped) {
Log.d(CashAdapter.TAG, "${CashAdapter.TAG}: isOverlapping : $positionss")
binding.tvItemCost.textSize = resizeTextDown(binding.tvItemCost)
// always assumed values in SP units
} else {
Log.d(CashAdapter.TAG, "${CashAdapter.TAG}: isNotOverlapping : $positionss")
}
}
}
private fun resizeTextDown(target: View): Float {
val t = target as TextView
val z = t.textSize
val a = z.pxToSp()
val b = a - 2.0F
// leave it as SP unit will do**
return b
}
}
interface ClickedListener {
fun onItemClick(cash: Cash)
}
fun isOverlapped(tvItemCost: View, g20: View): Boolean {
val firstPosition = IntArray(2)
val secondPosition = IntArray(2)
tvItemCost.getLocationOnScreen(firstPosition)
g20.getLocationOnScreen(secondPosition)
val rectFirstView = Rect(
firstPosition[0],
firstPosition[1],
firstPosition[0] + tvItemCost.measuredWidth,
firstPosition[1] + tvItemCost.measuredHeight
)
val rectSecondView = Rect(
secondPosition[0],
secondPosition[1],
secondPosition[0] + g20.measuredWidth,
secondPosition[1] + g20.measuredHeight
)
return rectFirstView.intersect(rectSecondView)
}
// plain basic recycler view adapter's methods
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CashViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CashListRowBinding.inflate(layoutInflater, parent, false)
return CashViewHolder(binding)
}
override fun onBindViewHolder(holder: CashViewHolder, position: Int) {
val cash = cashList[position]
holder.bind(cash, clickedAction, position)
}
override fun getItemCount(): Int {
return cashList.size
}
}
cash_list_row.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="cash"
type="com.elliot.financetracker.Cash" />
</data>
<androidx.cardview.widget.CardView
android:id="#+id/cv_cash_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="#color/colorLightGreen"
app:cardElevation="10dp"
app:cardCornerRadius="#dimen/default_card_corner_radius"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="#+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="#{cash.description}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="#id/g_20"
app:layout_constraintTop_toBottomOf="#id/tv_date"
tools:text="Item description Item description Item description Item description" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/g_20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.7"/>
<View
android:id="#+id/separator_cost"
android:layout_width="1dp"
android:layout_height="0dp"
android:padding="20dp"
android:background="#android:color/darker_gray"
app:layout_constraintLeft_toRightOf="#id/g_20"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="#+id/tv_item_cost"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="#{cash.amountString}"
android:gravity="center_vertical"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="RM -2000000000.00" />
<!-- removed out ...........-->
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
This can be perfectly solved by the auto resizing feature of TextView
use something like this
<TextView
android:layout_width="100dp"
android:layout_height="40dp"
android:maxLines="1"
android:text="hello world google"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="1sp"
app:autoSizeMaxTextSize="25sp"
android:gravity="center_vertical"
/>
I am trying to create a tabular layout for my Android application through an adapter class. However, the layout represented in the ViewPager does not change when I click on different tabs. Below is my code:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="#ffffff"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay"/>
<ScrollView 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
tools:context=".MainActivity"
app:tabTextAppearance="#style/MyCustomTextAppearance"
>
<!-- The tabular layout -->
<com.google.android.material.tabs.TabLayout
android:id="#+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textAllCaps="false"
android:background="#ffffff"
>
</com.google.android.material.tabs.TabLayout>
<!-- Contains the fragment corresponding to each tab -->
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
total_fragment.xml, individual_fragment.xml, usage_fragment.xml are all very basic layouts with just a TextView in them.
Below is my Kotlin code (MainActivity.kt):
import PageAdapter
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import android.util.Log
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
var tabLayout: TabLayout? = null
var viewPagerReference: ViewPager? = null
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tabLayout = findViewById<TabLayout>(R.id.tablayout)
viewPagerReference = findViewById<ViewPager>(R.id.viewPager)
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabA"))
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabB"))
tabLayout!!.addTab(tabLayout!!.newTab().setText("TabC"))
tabLayout!!.tabGravity = TabLayout.GRAVITY_FILL
val adapter = PageAdapter(this, supportFragmentManager, tabLayout!!.tabCount)
viewPager!!.adapter = adapter
viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
tabLayout!!.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
Log.i("TextStats","NEW TAB SELECTED: " + tab.position)
viewPager!!.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
}
For the adapter class (PageAdapter.kt):
import android.content.Context
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import com.example.textstats.IndividualFragment
import com.example.textstats.TotalFragment
import com.example.textstats.UsageFragment
class PageAdapter(private val myContext: Context, fm: FragmentManager, internal var totalTabs: Int) : FragmentPagerAdapter(fm) {
// this is for fragment tabs
override fun getItem(position: Int): Fragment? {
Log.i("TextStats", "POSITION = " + position);
when (position) {
0 -> {
// val homeFragment: HomeFragment = HomeFragment()
return TotalFragment()
}
1 -> {
return IndividualFragment()
}
2 -> {
// val movieFragment = MovieFragment()
return UsageFragment()
}
else -> return null
}
}
// this counts total number of tabs
override fun getCount(): Int {
return totalTabs
}
}
TotalFragment, UsageFragment, and IndividualFragment have all the same code below, except for the layout id being different (R.layout.[layout name]):
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class TotalFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.total_fragment, container, false)
}
}
add this line after set adapter
tabLayout.setupWithViewPager(viewPager)
class LayoutPagerAdapter internal
constructor(var mContext: Context):PagerAdapter() {
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object` as ImageView
}
private val sliderImageId = intArrayOf(
R.drawable.sym_action_call,R.drawable.presence_audio_away
)
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val imageView = ImageView(mContext)
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP)
imageView.setImageResource(sliderImageId[position])
(container as ViewPager).addView(imageView, 0)
return imageView
}
override fun destroyItem(
container: ViewGroup,
position: Int,
`object`: Any
) {
(container as ViewPager).removeView(`object` as ImageView)
}
override fun getCount(): Int {
return sliderImageId.size
}
}