I tried to add groupie adapter to my recycler view as shown in the code below. However when i run my application, i get a compiler error saying my reyclerview cannot be null (because of kotlin null safe feature). I cant figure out why this adapter is not working.
I know the problem is in the line :
recyclerview_newmessage.adapter = adapter
Here's the full code
class NewMessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.title = "Select User"
fetchUsers()
}
private fun fetchUsers() {
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<ViewHolder>()
p0.children.forEach {
Log.d("NewMessage", it.toString())
val user = it.getValue(User::class.java)
if (user != null) {
adapter.add(UserItem(user))
}
}
recyclerview_newmessage.adapter = adapter
}
})
}
}
class UserItem(val user: User): Item<ViewHolder>() {
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.itemView.username_textview_new_message.text = user.username
}
override fun getLayout(): Int {
return R.layout.user_row_new_message
}
}
The final result show display the rows in the recycler view with the usernames that are in my firebase database.
If you define the recycler view in the xml file then you need to add it.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ref_xml_file) . // add this line
supportActionBar?.title = "Select User"
fetchUsers()
}
if not define into the xml file.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var recyclerview_newmessage = RecyclerView(this)
setContentView(recyclerview_newmessage) . // add this line
supportActionBar?.title = "Select User"
fetchUsers()
}
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed last month.
An error occurs on line 41 when initializing the SongListFragment fragment class.
Here is a snippet of the class:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}
stack trace:
Fatal Exception: java.lang.NullPointerException
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.getBinding (SongListFragment.kt:41)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.access$getBinding (SongListFragment.kt:38)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment$setSongCount$1.invokeSuspend (SongListFragment.kt:225)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:234)
kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:166)
kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:397)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:431)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:420)
kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched (CancellableContinuationImpl.kt:518)
kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run (Runnable.kt:19)
This happens on the first run after installation. And what's more, everything is fine on physical devices with Android 10 on all others and on emulators as well.
What is the error, please help?
The crash is not in the above lines.. However, you are accessing binding after you navigate the screen.
You need to safe call isAdded before using binding as follow: e.g.
if(isAdded){
binding.button.setOnClickListener{ ... }
}
Try to move your UI elements bind logic to onViewCreated
See Fragment | Android Developers
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
For instance:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}
I have bottomsheet dialog for choosing file but, i don't know how to implement file picker in it, i want to make as in screenshot below.I have been looking for answer but i found only some libraries but it is simple dialog not bottomsheet.
Please Help.Thanks in advance
You can use Simple Storage's file picker. It supports scoped storage.
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
storageHelper.onFolderSelected = { requestCode, folder ->
// do stuff
}
storageHelper.onFileSelected = { requestCode, file ->
// do stuff
}
btnOpenFolderPicker.setOnClickListener { storageHelper.openFolderPicker() }
btnOpenFilePicker.setOnClickListener { storageHelper.openFilePicker() }
}
override fun onSaveInstanceState(outState: Bundle) {
storageHelper.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storageHelper.onRestoreInstanceState(savedInstanceState)
}
}
My question is about Mapbox. In this period I am working on an ANDROID application based on mapbox, using Kotlin and Fragments and my problem concerns the visualization of points on the map itself. That is my need is to be able to show points on the map through a GEOJSON file, for now I have been able to see the map in full in the application, but I cannot find a way to show the points taken from a GeoJson file and locate myself in the map via a button.
I should implement both functions in the fragment, so my problem is precisely that of not being able to show the points of a geojson file and find a way to locate myself in the map itself. I await help if there is someone able to help me with this problem, I also leave the code of the fragment class in kotlin.
Thanks everyone in advance !!
FRAGMENT HOME
class HomeFragment : Fragment() {
private var mapView: MapView? = null
#Nullable
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View? {
Mapbox.getInstance(
context!!.applicationContext,
"MIO CODICE MAPBOX"
)
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
mapView = view.findViewById<View>(R.id.mapView) as MapView
mapView!!.onCreate(savedInstanceState)
return view
}
override fun onResume() {
super.onResume()
mapView!!.onResume()
}
override fun onPause() {
super.onPause()
mapView!!.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView!!.onSaveInstanceState(outState)
}
override fun onLowMemory() {
super.onLowMemory()
mapView!!.onLowMemory()
}
override fun onDestroyView() {
super.onDestroyView()
mapView!!.onDestroy()
}
}
HOST ACTIVITY :
class HostActivity : AppCompatActivity() {
lateinit var googleSignInClient: GoogleSignInClient
private lateinit var navController: NavController
private val mAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private lateinit var drawerLayout: DrawerLayout
private lateinit var navViewBinding: DrawerHeaderLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
val toolbar = customToolbar
setSupportActionBar(toolbar)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
drawerLayout = drawer_layout
navViewBinding = DrawerHeaderLayoutBinding.inflate(layoutInflater, navView, true)
val navHost =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHost.navController
val navInflater = navController.navInflater
val graph = navInflater.inflate(R.navigation.main_graph)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.onBoarding ||
destination.id == R.id.authFragment ||
destination.id == R.id.loginFragment ||
destination.id == R.id.signUpFragment
) {
toolbar.visibility = View.GONE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} else {
toolbar.visibility = View.VISIBLE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
if (!Prefs.getInstance(this)!!.hasCompletedWalkthrough!!) {
if (mAuth.currentUser == null) {
graph.startDestination = R.id.authFragment
} else {
getUserData()
graph.startDestination = R.id.homeFragment
}
} else {
graph.startDestination = R.id.onBoarding
}
navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
navView.setupWithNavController(navController)
navView.setNavigationItemSelectedListener {
it.isChecked
drawerLayout.closeDrawers()
when (it.itemId) {
R.id.action_logout -> {
MyApplication.currentUser!!.active = false
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
mAuth.signOut()
}
googleSignInClient.signOut()
MyApplication.currentUser = null
navController.navigate(R.id.action_logout)
}
}
true
}
}
private fun getUserData() {
val ref = db.collection("users").document(mAuth.currentUser!!.uid)
ref.get().addOnSuccessListener {
val userInfo = it.toObject(UserModel::class.java)
navViewBinding.user = userInfo
MyApplication.currentUser = userInfo
MyApplication.currentUser!!.active = true
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
}
}.addOnFailureListener {
val intent = Intent(this, MyApplication::class.java)
startActivity(intent)
finish()
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, drawerLayout)
}
}
You'll need to use a GeoJsonSource. https://github.com/mapbox/mapbox-android-demo/search?q=GeoJsonSource shows how the demo app uses the source.
https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt (Its XML layout file)
Putting icons on the map. https://docs.mapbox.com/android/maps/examples/marker-symbol-layer/. Do all of the icon setup inside of the fragment's onStyleLoaded() callback as seen at https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt#L51-L55
https://github.com/mapbox/mapbox-android-demo/search?q=loadGeojson shows how the demo app loads from a GeoJson file. You could use coroutines instead of building out the AsyncTask.
Although it's in Java, https://docs.mapbox.com/android/maps/examples/show-a-users-location-on-a-fragment/ shows how to combine the Maps SDK's LocationComponent with a fragment.
Regarding moving the camera to the device's last known location when a button is clicked, see my answer at https://stackoverflow.com/a/64159178/6358488
I have a RecyclerView implemented with the Groupie library and I can delete an item from the list fine, however need to update the view to see the change. I'd like to have something like notifyDataSetChanged() instead, so the list updates immediately. I'm a bit confused at this stage though, tried a few different ways to get an interface from the class that hosts my view holder to be triggered from the fragment that holds the adapter but I think I'm stuck now if I could get some help please.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(position)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(position: Int)
}
}
Here it's my fragment. I tried to add a section to the adapter to see if it would allow me to retrieve a listener for it, but as my listener should be triggered under a specific item within the layout, this may not be the best solution, although couldn't make this work either.
class ProductsListFragment : Fragment(), RecyclerProductItem.AdapterListener {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
private val section = Section()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
adapter.add(section)
recyclerView.adapter = adapter
loadProducts()
}
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback {
override fun onSuccess(productList: List<JsonObject>) {
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
),adapterListenerToBePassedHere
)
) // This part is where I should be giving the listener, but get a red line since not sure how to get it to include it here.
}
}
})
}
companion object {
fun newInstance(): ProductsListFragment {
return ProductsListFragment()
}
}
override fun onClickItem(position: Int) {
adapter.notifyItemRemoved(position)
}
}
Many thanks.
I think you are missing this concept from the groupie Readme:
Modifying the contents of the GroupAdapter in any way automatically sends change notifications. Adding an item calls notifyItemAdded(); adding a group calls notifyItemRangeAdded(), etc.
So to remove an item, call section.remove(item). However, in your onClickItem function you currently only pass the position. Pass the item like clickListener?.onClickItem(this#RecyclerProductItem) instead.
Even more ideally and safely you should remove by product.id, e.g. clickListener?.onClickItem(this#RecyclerProductItem.product.id) then in onClickItem() you just search for the item with that product id and remove it. Let me know if I'm not clear.
Based on #carson's reply, this is what worked for me. Had to add the items to the section, the section to the adapter and then remove the item from the section based on the adapter position once that listener is clicked, passing the method that implements the listener as one of the arguments to complete the GroupAdapter.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
tvTitle.text = product.title
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(this#RecyclerProductItem.product.id, adapterPosition)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(id: Int, position: Int)
}
}
And
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.AdapterListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "onSuccess $LOG_TAG")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
section.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
adapter.add(section)
recyclerView.adapter = adapter
}
}
override fun onFailure() {
Log.e(LOG_TAG, "onFailure $LOG_TAG")
}
override fun onError() {
Log.e(LOG_TAG, "onError $LOG_TAG")
}
override fun onClickItem(id: Int, position: Int) {
section.remove(adapter.getItem(position))
}
})
}
I am trying to make a notes app which will allows the user make simple notes and save it. I used realm database to save the users notes. The notes are showing up fine in the recyclerview but I cannot click them.
What I want to happen is, When one of the notes in the recyclerview is clicked I want to start a new acitivty.
adapter
class NotesAdapter(private val context:Context, private val notesList: RealmResults<Notes>):RecyclerView.Adapter<RecyclerView.ViewHolder>(){
private lateinit var mOnClickListener:View.OnClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.notes_rv_layout,parent,false)
v.setOnClickListener(mOnClickListener)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return notesList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.titleTV.text = notesList[position]!!.title
holder.itemView.descTV.text = notesList[position]!!.description
holder.itemView.idTV.text = notesList[position]!!.id.toString()
}
private fun onClick(view:View) {
}
class ViewHolder(v: View?):RecyclerView.ViewHolder(v!!){
val title = itemView.findViewById<TextView>(R.id.titleTV)
val desc = itemView.findViewById<TextView>(R.id.descTV)
val id = itemView.findViewById<TextView>(R.id.idTV)
}
}
main activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//init views
realm = Realm.getDefaultInstance()
addNotes = findViewById(R.id.addNotes)
notesRV = findViewById(R.id.NotesRV)
//onclick add notes button
addNotes.setOnClickListener {
val intent = Intent(this, AddNotesActivity::class.java)
startActivity(intent)
finish()
}
notesRV.layoutManager = StaggeredGridLayoutManager(2,LinearLayoutManager.VERTICAL)
getAllNotes()
}
private fun getAllNotes() {
notesList = ArrayList()
val results:RealmResults<Notes> = realm.where<Notes>(Notes::class.java).findAll()
notesRV.adapter = NotesAdapter(this, results)
notesRV.adapter!!.notifyDataSetChanged()
}
}
use onBindViewHolder for that:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.titleTV.text = notesList[position]!!.title
holder.itemView.descTV.text = notesList[position]!!.description
holder.itemView.idTV.text = notesList[position]!!.id.toString()
holder.itemView.setOnClickListener{
val intent = Intent(holder.itemView.context,YourActivity::class.java)
holder.itemView.context.startActivity(intent)
}
}