I try to populate an array with data from a room database.
The data is only populated in the recyclerview after I refresh the
fragment. Once refreshed, everything is displayed. Anyone know why?
So the weird thing is that everything works fine but only once the fragment is refreshed.
I already implemented the notifydataset changed function but without success.
class WorkoutFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
private var _binding: FragmentWorkoutBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
#SuppressLint("NotifyDataSetChanged")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentWorkoutBinding.inflate(inflater, container, false)
val root: View = binding.root
val sharedPreference = activity?.getSharedPreferences("WORKOUT_BUTTON", Context.MODE_PRIVATE)
val sharedPreference2 = activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
var editor = sharedPreference?.edit()
val recyclerView = binding.recyclerview
recyclerView?.layoutManager = LinearLayoutManager(context)
var workout: MutableList<String> = mutableListOf()
var workoutid: MutableList<Int> = mutableListOf()
workout.clear()
workoutid.clear()
val adapter = AdapterWorkoutsHeader(workoutid as ArrayList<Int>,
workout as ArrayList<String>
)
recyclerView?.adapter = adapter
val user = ParseUser.getCurrentUser()
val parkname = user.get("parkname")
val currentSpot: TextView = binding.currentSpot
currentSpot.text = parkname.toString()
val openNav: ImageButton = binding.openNav
openNav.setOnClickListener {
val mDrawerLayout: DrawerLayout? = activity?.findViewById(R.id.drawer_layout)
mDrawerLayout!!.openDrawer(GravityCompat.START)
}
val addWorkout: Button = binding.button
addWorkout.setOnClickListener {
val intent = Intent(requireContext(), ChooseWorkoutActivity::class.java)
startActivity(intent)
}
Thread{
val db = Room.databaseBuilder(
requireContext(),
AppDatabase::class.java, "workout.db"
).build()
val userDao = db.todoDao()
for (i in 0 until userDao.getWorkoutSize()){
userDao.getChosenWorkout(i)[0].workout?.let { workout.add(it) }
userDao.getChosenWorkout(i)[0].workoutid?.let { workoutid.add(it) }
activity?.runOnUiThread {
val adapter = AdapterWorkoutsHeader(workoutid as ArrayList<Int>,
workout as ArrayList<String>
)
recyclerView?.adapter = adapter
adapter.notifyDataSetChanged()
}
}
//var parkNames2 = parkNames.toTypedArray()
//recyclerView.adapter?.notifyDataSetChanged()
}.start()
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Data updating should be done from the UI thread
runOnUiThread {
recyclerView?.adapter = adapter
adapter.notifyDataSetChanged()
}
Synchronization problem
var workout: MutableList<String> = mutableListOf()
var workoutid: MutableList<Int> = mutableListOf()
is on UI thread, but modified from the other thread, so synchronization or synchronized collections Collections.synchronizedList can be used
Related
I have a ShopFilterFragmentProductFilter which is inside a ShopFilterFragmentHolder which itself holds a ViewPager2. This ShopFilterFragmentHolder is a DialogFragment which is opened inside my ShopFragment. So ShopFragment -> ShopFilterFragmentHolder (Dialog, ViewPager2) -> ShopFilterFragmentProductFilter. ALL of these Fragments should share the same navgraphscoped viewmodel.
The problem I have is, that when I attach an observer inside my ShopFilterFragmentProductFilter to get my recyclerview list from cloud-firestore, this observer never gets called and therefore I get the error message "No Adapter attached, skipping layout". I know that this is not a problem with how I instantiate and assign the adapter to my recyclerview, because when I set a static list (e.g creating a list inside my ShopFilterFragmentProductFilter) everything works.
Why do I don't get the livedata value? To my mind, there is a problem with the viewmodel creation.
Here is my current approach:
ShopFilterFragmentProductFilter
#AndroidEntryPoint
class ShopFilterFragmentProductFilter : Fragment() {
private var _binding: FragmentShopFilterItemBinding? = null
private val binding: FragmentShopFilterItemBinding get() = _binding!!
private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
#Inject lateinit var shopFilterItemAdapter: ShopFilterItemAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShopFilterItemBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
submitAdapterList()
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvShopFilter.adapter = null
_binding = null
}
private fun bindObjects() {
with(binding) {
adapter = shopFilterItemAdapter
}
}
private fun submitAdapterList() {
shopViewModel.shopProductFilterList.observe(viewLifecycleOwner) {
shopFilterItemAdapter.submitList(it)
shopFilterItemAdapter.notifyDataSetChanged()
toast("SUBMITTED LIST") // this does never get called
}
/* // this works
shopFilterItemAdapter.submitList(
listOf(
ShopFilterItem(0, "ITEM 1"),
ShopFilterItem(0, "ITEM 2"),
ShopFilterItem(0, "ITEM 3"),
ShopFilterItem(0, "ITEM 4"),
ShopFilterItem(0, "ITEM 5"),
)
)
*/
}
}
ViewModel
class ShopViewModel #ViewModelInject constructor(
private val shopRepository: ShopRepository,
private val shopFilterRepository: ShopFilterRepository
) : ViewModel() {
private val query = MutableLiveData(QueryHolder("", ""))
val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }
val shopProductFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getProductFilterList() }
val shopListFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getListFilterList() }
fun search(newQuery: QueryHolder) {
this.query.value = newQuery
}
}
ShopFilterRepositoryImpl
class ShopFilterRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : ShopFilterRepository {
override suspend fun getProductFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.productFilter
override suspend fun getListFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.listFilter
}
Nav_graph
Probably, you should define it as MutableLiveData:
private val shopProductFilterList: MutableLiveData<List<ShopFilterItem>> = MutableLiveData()
And in a method in your viewModel that gets the data through repository, you should post the LiveData value:
fun getProductFilterList() = viewModelScope.launch {
val dataFetched = repository. getProductFilterList()
shopProductFilterList.postValue(dataFetched)
}
This is my first time building an application. I want to display my data in firebase realtimedatabase in recyclerview. but 'E/RecyclerView: No adapter attached; skipping layout' is on the Run chart.
I'll show you my codes in order.
at first, this is my Data class in kotlin
data class BalInputDTO(
var Id : String? = null,
var Itype: String? = null,
var Icategory: String? = null,
var ldate : String? = null,
var balance: String? = null,
var commnet: String? = null)
and then this is my adapter.kt
class BalAdapter(val context: Context, val BalList: ArrayList<BalInputDTO>) :
RecyclerView.Adapter<BalAdapter.Holder>() {
override fun onBindViewHolder(holder: Holder, position: Int) {
holder?.bind(BalList[position], context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.household_detail, parent, false)
return Holder(view)
}
override fun getItemCount(): Int {
return BalList.size
}
inner class Holder(view: View?) : RecyclerView.ViewHolder(view!!) {
val recordCategory = view?.findViewById<TextView>(R.id.record_category)
val recordNote = view?.findViewById<TextView>(R.id.record_note)
val recordAmount = view?.findViewById<TextView>(R.id.record_amount)
val recordDate = view?.findViewById<TextView>(R.id.record_date)
val recordDeleteImageView = view?.findViewById<ImageButton>(R.id.record_delete_image_button)
fun bind(bal: BalInputDTO, context: Context) {
recordCategory?.text = bal.Icategory
recordNote?.text = bal.commnet
recordAmount?.text = bal.balance
recordDate?.text = bal.ldate
// recordDeleteImageView.imageb
}
}
}
and the last code. this is my Fragment.kt (only onCreatView part)
var fragmentView : View? = null
var firedatabase : FirebaseDatabase? = null
var BalList : ArrayList<BalInputDTO> ? = null
var ref : DatabaseReference? = null
var mRecyclerView : RecyclerView? =null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView= LayoutInflater.from(activity).inflate(R.layout.fragment_household, container, false)
firedatabase = FirebaseDatabase.getInstance()
mRecyclerView = fragmentView?.findViewById(R.id.household_recyclerview)
mRecyclerView?.setHasFixedSize(true)
mRecyclerView?.layoutManager = LinearLayoutManager(context)
BalList = arrayListOf<BalInputDTO>()
ref = FirebaseDatabase.getInstance().getReference("BalInput")
ref?.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onDataChange(p0: DataSnapshot) {
if(p0!!.exists()){
for (h in p0.children){
val bal = h.getValue(BalInputDTO::class.java)
BalList?.add(bal!!)
}
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())
mRecyclerView?.setAdapter(adapter)
}
}
})
return fragmentView
}
and this is my database enter image description here
Please let me know if I missed something or did something wrong.
It seems like you passing the wrong list to your adapter. Try this
val adapter = BalAdapter(context!!,BalList)
instead of
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())
I'd say the real issue here is this line
val adapter = BalAdapter(context!!,BalList = ArrayList<BalInputDTO>())
try changing it to:
val adapter = BalAdapter(context!!,BalList)
since this is where you are adding all the elements from FB.
So I have a ViewModel that retrieve query for search API. For that, I also have SearchView but when typing the first letter on SearchView the app crashed because KotlinNullPointer on this line inside retrofit
resultsItem?.value = resultsItemList as List<ResultsItem>?
I think I have done everything right, I tried
Creating own method to pass data to ViewModel
Using intent to pass data to ViewModel
Defining default value inside ViewModel which works, but can't change after defined
Here is the code for the Fragment
class Search : Fragment() {
var searchAdapter: SearchAdapter? = null
lateinit var recyclerView: RecyclerView
lateinit var model: picodiploma.dicoding.database.picodiploma.dicoding.database.search.adapter.SearchView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_search, container, false)
recyclerView = view.findViewById(R.id.search_result_tv)
val layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
model = ViewModelProviders.of(this).get(picodiploma.dicoding.database.picodiploma.dicoding.database.search.adapter.SearchView::class.java)
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.search, menu)
val searchItem = menu.findItem(R.id.search_)
val searchView = searchItem?.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(s: String): Boolean {
return false
}
override fun onQueryTextChange(s: String): Boolean {
model.query = s
getViewData()
return true
}
})
}
fun getViewData() {
model.getData().observe(this, Observer { resultsItem ->
searchAdapter = SearchAdapter((resultsItem as ArrayList<ResultsItem>?)!!, this.context!!)
recyclerView.adapter = searchAdapter
recyclerView.visibility = View.VISIBLE
})
}
}
And the ViewModel
class SearchView : ViewModel() {
private val API_KEY = "2e08750083b7e21e96e915011d3f8e2d"
private val TAG = SearchView::class.java.simpleName
lateinit var query: String
companion object {
var resultsItem: MutableLiveData<List<ResultsItem>>? = null
}
fun getData(): LiveData<List<ResultsItem>> {
if (resultsItem == null) {
resultsItem = MutableLiveData()
loadData()
}
return resultsItem as MutableLiveData<List<ResultsItem>>
}
private fun loadData() {
val apiInterface = ApiClient.getList().create(ApiInterface::class.java)
val responseCall = apiInterface.getTvSearch(API_KEY, query)
responseCall.enqueue(object : Callback<Response> {
override fun onResponse(call: Call<Response>, response: retrofit2.Response<Response>) {
val resultsItemList = response.body()!!.results
resultsItem?.value = resultsItemList as List<ResultsItem>?
}
override fun onFailure(call: Call<Response>, t: Throwable) {
Log.d(TAG, t.toString())
}
})
}
}
What am I doing wrong?
Seems like you defined resultsItem as nullable MutableLiveData, but the List<ResultsItem> inside your LiveData is not nullable.
So I guess your resultsItemList is null when you get response from the server. And you are getting KotlinNullPointer because you are trying to assign null to notNull value of resultsItem LiveData.
Change below line
var resultsItem: MutableLiveData<List<ResultsItem>>? = null
to
var resultsItem: MutableLiveData<List<ResultsItem>>? = MutableLiveData()
Put everything inside apply
run{
searchAdapter = SearchAdapter((resultsItem as ArrayList<ResultsItem>?)!!, this.context!!)
recyclerView.adapter = searchAdapter
recyclerView.visibility = View.VISIBLE
}
This question has been asked a lot but in each solution i can't find a good way to implement it.
Basically i have my FragmentPagerAdaper
internal class FragmentPagerAdapter(fm: androidx.fragment.app.FragmentManager, private val mNumbOfTabs: Int) : androidx.fragment.app.FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return when (position) {
HOME -> HomeFragment()
SHOPPING_CART -> ShoppingCartFragment()
/*COLLECTION -> CollectionFragment()
USER_SETTINGS -> UserSettingsFragment()*/
else -> HomeFragment()
}
}
override fun getCount(): Int {
return mNumbOfTabs
}
companion object {
private const val HOME = 0
private const val SHOPPING_CART = 1
private val COLLECTION = 1
private val USER_SETTINGS = 2
}
}
and the ShoppingCartFragment
class ShoppingCartFragment : Fragment() {
private var inputFragmentView: View? = null
var products: ArrayList<Any> = ArrayList()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val frameLayout = FrameLayout(this.context!!)
populateViewForOrientation(inflater,frameLayout)
return frameLayout
}
private fun RecyclerAnimator(recyclerView: RecyclerView, adapter: ProductCartViewAdapter) {
val itemAnimator = DefaultItemAnimator()
itemAnimator.addDuration = 1000
itemAnimator.removeDuration = 1000
recyclerView.itemAnimator = itemAnimator
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(context)
val divider = requireContext().resources.getDimensionPixelSize(R.dimen.divider_size_default)
recyclerView.addItemDecoration(DividerItemDecorator(divider, SPACE_BOTTOM))//SPACE_LEFT or SPACE_TOP or SPACE_RIGHT or SPACE_BOTTOM concat
}
fun checkList(){
products.clear()
products = database.retrieveShoppingCartList()
val emptyImage = inputFragmentView!!.findViewById<ImageView>(R.id.emptyList)
val recyclerList = inputFragmentView!!.findViewById<RecyclerView>(R.id.shopping_rv)
if (products.isEmpty() || products.size == 0){
recyclerList.visibility = GONE
recyclerList.removeAllViews()
emptyImage.visibility = VISIBLE
} else{
recyclerList.visibility = VISIBLE
emptyImage.visibility = GONE
val adapter = ProductCartViewAdapter(products, context!!, this)
RecyclerAnimator(recyclerList, adapter)
}
}
private fun populateViewForOrientation(inflater: LayoutInflater, viewGroup: ViewGroup) {
viewGroup.removeAllViewsInLayout()
inputFragmentView = inflater.inflate(R.layout.fragment_shopping_cart, viewGroup)
checkList()
}
}
until here everything goes well. This is how the app looks.
But when i reselect that Tab that contains the ShoppingCartFragment i need to refresh the list. to be more specific call the function FragmentPagerAdapter.checkList().
But each time that i try to call that function from the fragment i keep receiving a NullPointer due to the fragment context that cannot be found...
in this:
products = database.retrieveShoppingCartList()
and this is how i handle those context using a synchronizer in the getInstance
SQLiteHandler
private var instance: SQLiteHandler? = null
#Synchronized
fun getInstance(ctx: Context): SQLiteHandler {
if (instance == null) {
instance = SQLiteHandler(ctx.applicationContext)
}
return instance!!
}
// Access property for Context
val Context.database: SQLiteHandler
get() = SQLiteHandler.getInstance(applicationContext)
val androidx.fragment.app.Fragment.database: SQLiteHandler
get() = SQLiteHandler.getInstance(activity!!.applicationContext)
Or any way to recreate the fragment of the viewpager when the tab is reselected
This question already has answers here:
Android Fragment no view found for ID?
(40 answers)
Closed 4 years ago.
Its showing that no view found. But what does that I am not able to understand.
I think problem is in OnCreateView() function as there is only the parameter where view is passed.
Should I use try and catch method?
code for MainScreenFragment
class MainScreenFragment : Fragment() {
var getsongsList: ArrayList<Songs>?=null
var nowPlayingButtonBar: RelativeLayout?=null
var playPauseButton: ImageButton?=null
var songTitle: TextView?=null
var visibleLayout: RelativeLayout?=null
var noSongs: RelativeLayout?=null
var recyclerView: RecyclerView?=null
var myActivate: Activity?=null
var _mainScreenAdapter: MainscreenAdapter?=null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view= inflater.inflate(R.layout.main_screen_fragment,container,
false)
visibleLayout=view?.findViewById<RelativeLayout>(R.id.visibleLayout)
noSongs= view?.findViewById<RelativeLayout>(R.id.noSongs)
nowPlayingButtonBar= view?.findViewById<RelativeLayout>
(R.id.hiddenMainScreen)
songTitle = view?.findViewById<TextView>(R.id.songName)
playPauseButton= view?.findViewById<ImageButton>
(R.id.playPauseButton)
recyclerView=view?.findViewById<RecyclerView>(R.id.contentMain)
return view }
#RequiresApi(Build.VERSION_CODES.O)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
getsongsList = getSongsFromPhone()
_mainScreenAdapter= MainscreenAdapter(getsongsList as
ArrayList<Songs>, myActivate as Context)
val mLayoutManager = LinearLayoutManager(myActivate)
recyclerView?.layoutManager = mLayoutManager
recyclerView?.itemAnimator = DefaultItemAnimator()
recyclerView?.adapter = _mainScreenAdapter
}
override fun onAttach(context: Context?) {
super.onAttach(context)
myActivate = context as Activity
}
override fun onAttach(activity: Activity?) {
super.onAttach(activity)
myActivate= activity
}
#RequiresApi(Build.VERSION_CODES.O)
fun getSongsFromPhone(): ArrayList<Songs> {
val arrayList =ArrayList<Songs>()
val contentResolver = myActivate?.contentResolver
val songUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val songCursor = contentResolver?.query(songUri,null,null,null)
if(songCursor!= null && songCursor.moveToFirst()){
val songId =
songCursor.getColumnIndex(MediaStore.Audio.Media._ID)
val songTitle =
songCursor.getColumnIndex(MediaStore.Audio.Media.TITLE)
val songArtist =
songCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)
val songData =
songCursor.getColumnIndex(MediaStore.Audio.Media.DATA)
val songAdded =
songCursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED)
while (songCursor.moveToNext()){
val currentID =songCursor.getLong(songId)
val currentTitle =songCursor.getString(songTitle)
val currentArtist =songCursor.getString(songArtist)
val currentData =songCursor.getString(songData)
val currentAdded =songCursor.getLong(songAdded)
arrayList.add(Songs(currentID,
currentTitle,currentArtist,currentData,currentAdded))
songCursor.close()
}
}
return arrayList
}
}
No view found mostly comes when you are taking the wrong id of the FrameLayout which is on another XML file and replacing the fragment in that FrameLayout. Just check once that you are replacing your fragment in the correct FrameLayout.