I have an activity with a tabbed layout containing multiple different tabs. Each tab contains a number of 'edittext' fields. I have a button on the main activity and on clicking it i want to save the the contents of each edittext field from each tab. Currently i can return an ordinary value from the tab but i can not get the contents of the edittext field.
I have tried creating a 'lateinit var frag1_tenNo : EditText' within the fragment class for a tab. I have initialized it in the onCreateView but the program crashes saying 'lateinit property frag1_tenNo has not been initialized'
fragment code
class frag1: Fragment() {
lateinit var frag1_tenNo : EditText
override public fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewFrag1 = inflater.inflate(R.layout.frag1, container, false)
frag1_tenNo = viewFrag1.findViewById(R.id.survey_tenantNo)
return viewFrag1
}
fun saveFrag1Data(): String {
var data1 = frag1_tenNo.text.toString()
return data1
}
activity code - to retrieve data
save_btn.setOnClickListener {
Log.d("Survey","Change Button Clicked")
val test = frag1().saveFrag1Data()
Log.d("Survey","Returned value : $test")
}
val test = frag1().saveFrag1Data()
Here you create a new fragment. OnCreate method of this fragment isn't called yet. So quite predictably you get lateinit property frag1_tenNo has not been initialized error.
Related
I have an activity that is controlled with a navigation component, it has few fragments, inside one of these fragments there is a recyclerView that has some items, when I click on an Item I want it to navigate me to another fragment that has additional information about the item, I don't know how to use navigation component inside a recycelerView, when I type findNavController it has some parameters that am not sure what to put in or if its even the right function, I also tried to do it by code like this:
val fm = (context as AppCompatActivity).supportFragmentManager
fm.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(null)
.commit()
by the way this is the code that asks for other parameters:
// it asks for a (fragment) or (activity, Int)
findNavController().navigate(R.id.action_settingsFragment_to_groupUnits)
the problem is when I navigate out of this fragment or use the drawer navigation (nav component for the other fragments), this fragment that I navigated to stays displayed in the screen, I see both fragments at the same time, I assume its a fragment backStack issue but I don't know how to solve it, thanks for the help and your time in advance
You do not need to navigate from RecyclerView item click to AdditionalDetails fragment directly.
You can do this same thing by help of interface.
Steps:
Create an interface with a method declaration.
Extend Interface from the fragment where you are using your RecyclerView and Implement interface method.
Pass this interface with the adapter.
Using the interface from adapter you just pass object when click on item.
Finally from your fragment you just navigate to AdditionalDetails fragment with argument.
Lets see sample code from my current project:
Interface
interface ChatListClickListener {
fun onChatListItemClick(view:View, user: User)
}
Adapter Class
class UserAdapter(val Users: List<User>, val chatListClickListener: ChatListClickListener) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
inner class UserViewHolder(
val recyclerviewUsersBinding: RecyclerviewChatlistBinding
) : RecyclerView.ViewHolder(recyclerviewUsersBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val vh = UserViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.recyclerview_chatlist,
parent,
false
)
)
return vh
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.recyclerviewUsersBinding.user = Users[position]
holder.recyclerviewUsersBinding.root.setOnClickListener{
chatListClickListener.onChatListItemClick(it,Users[position])
}
}
override fun getItemCount(): Int {
return Users.size
}
}
My fragment
class FragmentChatList : Fragment(), ChatListClickListener {
lateinit var binding: FragmentChatListBinding
lateinit var viewModel: ChatListViewModel
lateinit var listener: ChatListClickListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val args: FragmentChatListArgs by navArgs()
binding = FragmentChatListBinding.inflate(layoutInflater, container, false)
val factory = ChatListFactory(args.user)
viewModel = ViewModelProvider(this, factory).get(ChatListViewModel::class.java)
binding.viewModel = viewModel
listener = this
lifecycleScope.launch {
viewModel.addUserWhenUserConnect()
}
viewModel.userList.observe(viewLifecycleOwner, Observer { data ->
binding.rvChatList.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
adapter = UserAdapter(data, listener)
}
})
return binding.root
}
override fun onChatListItemClick(view: View, user: User) {
Toast.makeText(requireContext(), user.name + "", Toast.LENGTH_SHORT).show()
// here you navigate to your fragment....
}
}
I guess this will be helpful.
I'm trying to migrate from Kotlin synthetics to View Binding. How should I View bind from two different layouts. I'm trying to connect a button from fragment Main layout and from custom dialog layout. Right now I can connect button id from fragment main layout.Example code below:
class MainFragment: Fragment(R.layout.fragment_main) {
private var fragmentMainBinding: FragmentMainBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Main fragment viewbinding
val binding = FragmentMainBinding.bind(view)
fragmentMainBinding = binding
// FAB button onClick Listener
binding.fabAddItem.setOnClickListener {
// Inflate add_item_dialog.xml custom view
val dialogView = LayoutInflater.from(activity).inflate(R.layout.add_item_dialog, null)
// Add AlertDialog Builder
val dialogBuilder = AlertDialog.Builder(activity)
.setView(dialogView)
//Show custom dialog
val customAlertDialog = dialogBuilder.show()
binding.btnClose.setOnClickListener {
customAlertDialog.dismiss()
}
}
}
}
If you want to get a hold of dialog binding you can do the following
val dialogBinding = AddItemDialogBinding.bind(dialogView)
I have created a simple notes app that uses some of the Android Architecture components. I am using dataBinding to set data to my recycler view. One of the functionalities is bookmarking a note and displaying it in the Bookmarks fragment. In the Bookmarks fragment a user can tap the 'unbookmark' icon to remove a bookmarked note. I have used a simple on click listener on the icon inside my Bookmarks Recycler View Adapter to achieve this. I have a boolean property in my notes entity. Inside the Bookmarks fragment I update the bookmarks boolean value to false when the user taps the icon so that it changes to a false value(meaning it's not bookmarked and does not appear in the bookmarks fragment). However, when I click the 'unbookmark' icon the recycler view still displays the notes removed from the bookmarks.
Here is my Bookmarks Adapter :
class BookmarksAdapter(private var bookmarksList: List<Note>, var context: Context):
RecyclerView.Adapter<BookmarksAdapter.BookmarkViewHolder>(), CoroutineScope {
private lateinit var job: Job
var deletedId : Int? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkViewHolder {
val layoutItemBinding: BookmarksLayoutItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.bookmarks_layout_item,
parent,
false)
job = Job()
return BookmarkViewHolder(layoutItemBinding.root)
}
override fun onBindViewHolder(holder: BookmarkViewHolder, position: Int) {
val bookmarkNote: Note = bookmarksList.get(position)
holder.bookmarkBinding?.setVariable(BR.bookmarkItem, bookmarkNote)
holder.bookmarkBinding?.executePendingBindings()
holder.bookmarkBinding?.imageRemoveBookmark?.setOnClickListener {
removeBookmark(bookmarkNote, position)
}
}
override fun getItemCount(): Int {
return bookmarksList.size
}
inner class BookmarkViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var bookmarkBinding: BookmarksLayoutItemBinding? = DataBindingUtil.bind(itemView)
}
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
fun deleteNote(note : Note) {
launch(Dispatchers.IO) {
note.isBookmarked = false
NotesDatabase(context).getDao().updateNote(note)
}
val pos = bookmarksList.toMutableList().indexOf(note)
bookmarksList.toMutableList().removeAt(pos)
notifyItemRemoved(pos)
}
// Remove a bookmark
fun removeBookmark(bookmarkNote: Note, removedPos: Int) {
bookmarkNote.isBookmarked = false
launch (Dispatchers.IO){
NotesDatabase(context).getDao().updateNote(bookmarkNote)
withContext(Dispatchers.Main) {
bookmarksList.toMutableList().removeAt(removedPos)
notifyItemRemoved(removedPos)
notifyItemRangeChanged(removedPos, 1)
}
}
}
}
Below is my Bookmarks View Model:
class BookmarksViewModel(application: Application) : AndroidViewModel(application) {
val myContext: Context = application.applicationContext
private var _bookmarksList = MutableLiveData<List<Note>>()
val bookmarksList : LiveData<List<Note>>
get() = _bookmarksList
init{
viewModelScope.launch {
_bookmarksList.value = getNotes()
}
}
suspend fun getNotes() : List<Note> = NotesDatabase(myContext).getDao().getBookmarkedNotes()
}
Here is my Bookmarks Fragment:
class BookmarksFragment : BaseFragment(){
private lateinit var bookmarksBinding: FragmentBookmarksBinding
private lateinit var bookmarksViewModel: BookmarksViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
bookmarksBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_bookmarks, container, false)
bookmarksViewModel = ViewModelProvider(this).get(BookmarksViewModel::class.java)
// Observe the list of bookmarks
bookmarksViewModel.bookmarksList.observe(viewLifecycleOwner, { bList ->
bookmarksBinding.listOfBookmarks = bList
})
return bookmarksBinding.root
}
}
Here is my Notes Entity:
val bookmarkSate: Boolean
get() = true
#Entity
data class Note(
val title : String,
val note : String,
var isBookmarked : Boolean = false
) : Serializable{
#PrimaryKey(autoGenerate = true)
var noteID : Int = 0
}
Here is my Notes DAO:
#Dao
interface NoteDao {
#Insert
suspend fun saveNote(note : Note)
#Query("SELECT * FROM note ORDER BY noteID DESC")
suspend fun getAllNotes() : List<Note>
#Query("SELECT * FROM note WHERE isBookmarked")
suspend fun getBookmarkedNotes() : List<Note>
// add multiple notes
#Insert
suspend fun addMultipleNotes(vararg note: Note)
#Update
suspend fun updateNote(note: Note)
#Delete
suspend fun deleteNote(note : Note)
}
I am stuck on how to achieve the desired functionality of 'unbookmarking' a note and making it 'disappear 'from the bookmarks fragment. Kindly anyone who can help out?
There are a few problems and possible solutions I see here:
1-) In your adapter, in your delete method, you have this piece of code:
val pos = bookmarksList.toMutableList().indexOf(note)
bookmarksList.toMutableList().removeAt(pos)
notifyItemRemoved(pos)
The problem here is that toMutableList() doesn't change your bookmarksList as mutable, it returns a new mutable list filled with the items in your bookmarkList, a mutable version of your list, but a new list! So you remove the item from this new list, and not from the original list. Simply correcting this could fix the problem. (You can define the bookmarksList as a mutable list from the beginning)
2-) You seem to observe the list from your viewmodel, it is livedata in your viewmodel. But it is not defined as livedata in your dao. So you are not observing the changes in the database. So an alternative solution could be to observe the changes from the database by wrapping your list within a livedata in your dao.
#Query("SELECT * FROM note WHERE isBookmarked")
suspend fun getBookmarkedNotes() : LiveData<List<Note>>
But this second one would require some additional changes in your code.
I have set the FragmentStatePageAdapter to swipe through fragments of the same layout and different data (users profiles).
When I give it list of 2 items all work well.
If I give it 3 or more items it creates views for fragments, but on third swipe contents turn blank, regardless to which direction I swipe (forward then forward, forward then backward). If I accurately slide to third item when still holding the screen I can see it's valid contents, but when I finish slide releasing the screen all disappears.
Page Adapter is set like below. (swipeList is list of users' ids, swipeEntityType == 0, each User contents is loaded by "uid" passed through bundle)
class SwipePagerFragment : Fragment() {
private lateinit var parent: MainActivity
private lateinit var state: State
private lateinit var ctx: Context
private lateinit var swipeList: MutableList<Int>
private lateinit var pager: ViewPager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_swipe_pager, container, false)
parent = activity as MainActivity
state = parent.state
ctx = state.ctx
swipeList = state.swipeList
pager = view.findViewById(R.id.fragmentSwipePager_pager)
val pagerAdapter = ScreenSlidePagerAdapter(childFragmentManager)
pager.adapter = pagerAdapter
return view
}
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
override fun getCount(): Int = swipeList.size
override fun getItem(position: Int): Fragment = when (state.swipeEntityType) {
0 -> {
makeUserFragment(swipeList[position])
}
else -> {
makeEventFragment(swipeList[position])
}
}
}
private fun makeUserFragment(uid: Int): UserFragment {
val bundle = Bundle()
bundle.putInt("uid", uid)
val frag = UserFragment()
frag.arguments = bundle
return frag
}
private fun makeEventFragment(eid: Int): EventFragment {
val bundle = Bundle()
bundle.putInt("eid", eid)
val frag = EventFragment()
frag.arguments = bundle
return frag
}
}
Setting
pager.offscreenPageLimit = swipeList.size
solves my problem
It is an activity hosting a pager fragment which has android.support.v4.view.ViewPager and the adapter class derived from FragmentStatePagerAdapter.
The problem is from the pager fragment it has two or three fragments cached, but the data is not parseable (including a view dynamically getting from a 3rd part sdk), when sometime os recreate this fragment in case like minimize/reopen it, or after rotation the restored fragment are blank (using the one from cache and lacking data).
Not find a way to re-populate the fragments restored by os through the pager fragment's recreation flow.
Even tried in the pager fragment to clear the adapter's data in onActivityCreated()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
the adapter:
fun setData(itemsList: List<Data>) {
this.data = itemsList
notifyDataSetChanged()
}
the viewPager seems still showing the previous cachhed fragment without expected data after the re-creation flow is complete. (tried to make sure it is from the cached one, by in the fragment's onsaveInstance() to save the position of the data in the adapter data list, and the re-created fragment in the viewPager got that position, so it is a os re-created one from cache. But how could it be after at beging already set the adapter with empty list at onActivityCreated()).
Cannt resolve with clean the fragments by setting data to empty.
So tried to remove the page fragment from the activity at onDestroy() with hope that there will be no fragment to be put in cache:
class ContentPagerFragment :Fragment {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.content_pager, container, false)
mContentViewPager = view.findViewById(R.id.contentViewPager)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
interface RemoveFragmentAtClose {
fun onContentPagerFragmentDoestroy()
}
override fun onDestroy() {
(activity as? RemoveAtCloseFragment)?
.onContentPagerFragmentDoestroy()
super.onDestroy()
}
}
and in the hosting activity
class HostActivity : AppCompatActivity(),
ContentPagerFragment.RemoveFragmentAtClose {
override fun onContentPagerFragmentDoestroy() {
supportFragmentManager.beginTransaction().
remove(supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer))
.commitAllowingStateLoss()
var contentPagerFragment = ContentPagerFragment::class.java.cast(supportFragmentManager
.findFragmentById(R.id.pagerFragmentContainer))
Log.i(TAG, " onContentPagerFragmentDoestroy(), this $this" +
"\ngetContentPagerFragment: $contentPagerFragment"
)
}
override fun onAttachFragment(fragment: Fragment) {
Log.d(TAG, " onAttachFragment(), " +
"fragment: $fragment \nthis $this")
}
}
But the log shows that the supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer) still find the fragment after the .commitAllowingStateLoss() call.
And when os restores the activity the ContentPagerFragment is showing up in the activity's onAttachFragment(fragment: Fragment) before activity's onCreate(savedInstanceState: Bundle?) is called.
Why is the viewPager using the old fragment after remove the pager fragment from the activity's supportFragmentManager?
How to prevent the view pager from use the cached fragment?