I am calling a DialogFragment in another fragment like bellow:
#CoursesFragmentScope
class CoursesFragment : DaggerFragment(),
DefaultLifecycleObserver,
CourseTypeListDialogFragment.ApplySelectedCourseTypeFilter {
#OnClick(R.id.btnCourseType)
fun btnCourseTypeClick() {
val fm = activity!!.supportFragmentManager
val courseTypeListDialogFragment =
CourseTypeListDialogFragment()
courseTypeListDialogFragment.isCancelable = false
courseTypeListDialogFragment.setStyle(
DialogFragment.STYLE_NO_TITLE,
0
)
courseTypeListDialogFragment.setTargetFragment(this, 1)
courseTypeListDialogFragment.show(fm, "")
}
}
And my DialogFragment is like bellow:
class CourseTypeListDialogFragment: DaggerAppCompatDialogFragment(), CourseTypeAdapter.CourseTypeListener {
interface ApplySelectedCourseTypeFilter {
fun applySelectedCourseType()
}
..some code..
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ButterKnife.bind(this, view)
iApplySelectedCourseTypeFilter = targetFragment as ApplySelectedCourseTypeFilter
}
..some code..
}
But get me bellow error in this line:
courseTypeListDialogFragment.setTargetFragment(this, 1)
And say me:
Fragment CourseTypeListDialogFragment{92f4a96 (cbe1689e-1367-4436-8de6-3797320e7d41) } declared target fragment CoursesFragment{422cc09 (9d16e30b-fd71-44a8-8c45-8a866b62c135) id=0x7f0800d5} that does not belong to this FragmentManager!
What can I do?
Both classes are from same package:
import dagger.android.support.DaggerAppCompatDialogFragment
import dagger.android.support.DaggerFragment
I removed courseTypeListDialogFragment.setTargetFragment(this, 1) and opened dialog but not work my interface in dialog.
I am using from fragments in activity like bellow:
class MainActivity : DaggerAppCompatActivity() {
#BindView(R.id.nav_view)
lateinit var navView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
super<DaggerAppCompatActivity>.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ButterKnife.bind(this)
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
navView.menu.findItem(R.id.navigation_courses).setIcon(
IconDrawable(this, MaterialCommunityIcons.mdi_account_box)
.colorRes(R.color.colorPrimaryDark)
.actionBarSize())
navView.menu.findItem(R.id.navigation_login).setIcon(
IconDrawable(this, MaterialCommunityIcons.mdi_login)
.colorRes(R.color.colorPrimaryDark)
.actionBarSize())
}
}
change this line
val fm = activity!!.supportFragmentManager
to
val fm = fragmentManager
read the following blog
https://medium.com/better-programming/what-is-target-fragment-da0e7c7f345c
Related
I have 2 fragments that i navigate throught navigation component from Jetpack. When i go back from fragment 2 to fragment 1, i fire an event throught livedata from fragment 2 and fragment1 should be observing. Althought, fragment 1 didnt observed and when i sste a breakpoint to check how many observers liveData has, it say 0.
Code below
class Fragment1 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
findNavController().navigate(
Fragment1Directions.actionToFragment2()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
private lateinit var myViewModel: MyViewModel
}
class Fragment2 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myButton.setOnClickListener{
MyViewModel().selectItem(true)
findNavController().navigateUp()
}
}
}
class MyViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Boolean>()
val selectedItem: LiveData<Boolean> get() = mutableSelectedItem
fun selectItem(value: Boolean) {
mutableSelectedItem.value = value
}
}
It's strange to not have observer for
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
when it's called onViewCreated and with viewLifecycleOwner, but it looks like you have a mistake for creating common ViewModel for Fragment1 and Fragment2
Both should use
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java) to get same ViewModel for both fragments
Fist of all, I am using bottomNavigation which has five fragments on the MainAcitvity .
The problem is down below.
I am using RegisterForActivityResult for gallery image in the fragmentA.
when I use that before changing to other fragments, It's perfectly fine.
However, after changing to different Fragments
and back to the fragmentA then call RegisterForActivityResult again, the callback is not triggered.
when my MainActivity is with in Condition1 I had a problem.
When my MainActivity is with in Condition2 RegisterForActivityResult callback was fine.
condition1----------------------------------------------------------------------
I Initialized fragment variables in the onCreate on MainActivity , and BottomNavigationListener as well.
Condition2----------------------------------------------------------------------
I Initialized fragment variables in the onStart on MainActivity , and BottomNavigationListener as well.
Does anybody know why?
** Problem Code ↓ condition1**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
** Working Code ↓ condition2**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
FragmentA code↓
class FragmentA : Fragment() {
// binding
lateinit var binding:FragmentABinding
//for instance from outside of the fragment
fun newInstance() : FragmentA{
return FragmentA()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = FragmentABinding.inflate(inflater, container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)
binding.button.setOnClickListener{
requestImagesFromGallery.launch("image/*")}
}
private val
RequestIamgeFromGallery=registerForActivityResult(ActivityResultContracts.GetContent()) {
//If callback is triggered, returned uri set into the Imageview
binding.mypageImage.setImageURI(uri)
}
}
Please give me some comments why those Fragments instance in the MainActivity onStart work, but onCreate is not working. Also is it ok if I use the condition2?
I tried to find an easy way to listen to fragment changes from my activity in order to hide/show the drawer menu button from my LoginFragment and I could not find a good and easy way to implement for my case here in sfo, so I would like to share an easy solution I eventually came up with using ViewModel and a LiveData which saves the fragment class name that is currently displayed and observing it from the activity to listen for changes.
NOTE the solution works in case that your fragments are displayed on the same FragmentContainerView in your layout
Here is an exmaple:
ViewModel class :
class MyViewModel : ViewModel(){
val currentFragment = MutableLiveData<String>()
}
Now set currentFragment value inside your fragments:
class LoginFragment() : Fragment() {
private lateinit var : viewModel : MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
ViewModel.currentFragment.value = this::class.java.name
}
}
class MainFragment() : Fragment() {
private lateinit var : viewModel : MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
ViewModel.currentFragment.value = this::class.java.name
}
}
Now in your Activity you can observe currentFragment and do whatever you want(in my case I wanted to know if the current fragment is LoginFragment and hide the drawer menu button from the toolbar) :
class MainActivity() : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapViewModel.currentFragment.observe(this, {
when (it) {
LoginFragment::class.java.name -> {
//your stuff related to LoginFragment
}
MainFragment::class.java.name -> {
//your stuff related to MainFragment
}
}
})
}
}
Hope this helps anyone ^^
I'm learning Jetpack by changing the Demo from codelabs.
What I changed is move the code of the MainActivity.kt into a fragment and jump between fragments.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(WordListFragment())
}
}
// Extension function to replace fragment
fun AppCompatActivity.replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.host,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
When we click the items, we will call replaceFragment inside WordListAdapter and go to another fragment as follows:
class WordListAdapter : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
...
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
holder.itemView.setOnClickListener {
// fire recyclerView click event
val activity = it.context as AppCompatActivity
val args = Bundle()
// Send string data as key value format
args.putString("word", current.word)
val fragment = WordDefinitionFragment()
fragment.arguments = args
activity.replaceFragment(fragment)
}
}
I just wondering if it's the right way to put replaceFragment inside the onBindViewHolder ?
In my opinion RecyclerView.Adapter should only bind immutable data and pass clicks via callbacks, and the Activity should be the one to change fragment. In my opinion you should do something like this:
class WordListAdapter(private val onViewHolderClicked: (String) -> Unit) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
...
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
holder.itemView.setOnClickListener {
onViewHolderClicked(current.word)
}
}
and in Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(WordListFragment())
}
...
fun setupRecyclerView() {
...
val adapter = WordListAdapter() { word ->
val args = Bundle()
// Send string data as key value format
args.putString("word", word)
val fragment = WordDefinitionFragment()
fragment.arguments = args
replaceFragment(fragment)
}
}
}
I would like to ask you for help. I am writing an application that uses MVVM and LiveData architecture. Inside ViewPager I have 3 fragments displaying data that comes from ViewModel. And I noticed that after connecting the viewModel to the activity and to the fragment, the data is updated only when the activity is started, but then Observe does not update the data even though the data has changed. After calling the next query to the server, inside onDataSet I send the appropriate time and obtains JSON data from the server, which it parses and passes to ViewModel. Why Fragment updates data only once in the beginning and nothing changes after?
This is the activity that hosts the fragments
class MainActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var navigationView: NavigationView
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fm = supportFragmentManager
currencyViewModel = ViewModelProvider
.AndroidViewModelFactory(application)
.create(CurrencyViewModel::class.java)
viewPager = findViewById(R.id.viewPager)
tabLayout = findViewById(R.id.tabLayout)
navigationView = findViewById(R.id.navigationView)
floatingActionButton = findViewById(R.id.floatingActionButton)
val viewPagerAdapter = CurrencyViewPagerAdapter(this)
viewPager.adapter = viewPagerAdapter
TabLayoutMediator(tabLayout
,viewPager
,TabLayoutMediator.TabConfigurationStrategy {
tab, position ->
when(position){
0 -> tab.text = "Tabela A"
1 -> tab.text = "Tabela B"
2 -> tab.text = "Tabela C"
}
}).attach()
floatingActionButton.setOnClickListener {
val dialog = CalendarFragment()
dialog.show(fm, "DatePickerDialog")
}
}
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
//Convert year,month,day to millisecounds
val c = Calendar.getInstance()
c.set(year,month,dayOfMonth)
val dayInMillis = c.time.time
val today = Calendar.getInstance()
if(checkIsDateAfterToday(today, c)){
CoroutineScope(Dispatchers.Main).launch {
currencyViewModel.setTableA(dayInMillis)
}
}
}
This is ViewModel common for activity and fragment
class CurrencyViewModel : ViewModel() {
private val repository = CurrencyRepository()
val tableA: MutableLiveData<Array<TableA>> by lazy {
MutableLiveData<Array<TableA>>().also {
loadTableA(Date().time)
}
}
private fun loadTableA(time: Long) {
CoroutineScope(Dispatchers.Main).launch {
val loadedData = CoroutineScope(Dispatchers.IO).async {
repository.getTableA(time)
}.await()
tableA.value = loadedData
}
}
fun setTableA(time: Long){
loadTableA(time)
}
}
And that's the fragment which displays data in recyclerView
class TableAFragment : Fragment() {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var recyclerViewA: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_table_a, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
currencyViewModel = ViewModelProvider.AndroidViewModelFactory
.getInstance(requireActivity().application)
.create(CurrencyViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerViewA = view.findViewById(R.id.recyclerView_A)
recyclerViewA.layoutManager = LinearLayoutManager(requireContext())
currencyViewModel.tableA.observe(viewLifecycleOwner, androidx.lifecycle.Observer{
val nbpAdapter = NBPAdapter(it)
recyclerViewA.adapter = nbpAdapter
})
}
}
Your instantiation of ViewModel is incorrect.
Should be
currencyViewModel = ViewModelProvider(this).get<CurrencyViewModel>() // lifecycle-ktx
and in Fragment:
currencyViewModel = ViewModelProvider(requireActivity()).get<CurrencyViewModel>() // lifecycle-ktx