I need to call a fragment method in the activity....
FirstFragment.kt
class FirstFragment: Fragment() {
fun getToast(context: Context) {
return Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show()
}
}
MainActivity.kt
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, FirstFragment, FistFragmentTAG_NAME)
transaction.commit()
//expecting to use fragment method...
parameter.getToast() //parameter is the code neede to call FirstFragment method .getToast()
}
}
Ive tried these 3 cases without success
Ive tried: (it crashes the app)
FirstFragment().getToast(this#MainActivity)
Also tried, but the function .getToast() appears red
val myToast = supportFragmentManager.findFragmentByTag(FistFragmentTAG_NAME).getToast(this#MainActivity)
Also tried, but the funtion .getToast() appears red as well
supportFragmentManager.fragments.forEach { it: Fragment
val getToast = it.getToast(this#MainActivity)
the problem here is not the context, because in the real, Im trying to call a method that doesnt use context at all... Context was set in this example just to call a Toast.
How do I call the fragment method??*
This is activity class
class MyActivity : AppCompatActivity() {
private lateinit var myFragment: FirstFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myFragment = FirstFragment()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, myFragment , "MyFragment")
.commit()
//this is fragment method, we call it from activity
myFragment.getToast(this)
}
}
This is fragment class
class FirstFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
fun getToast(context: Context) {
Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
}
}
Related
GameActivity
____FragmentQuest
____FragmentFight
class MapLvl.kt
this is a text RPG Fragment Quest displays a journey through different maps (changing the content in one fragment according to a template) text, pictures, navigation buttons. if there is a mob on the map, then the "Fight" button appears and a fragment of the turn-based fight FightFragment opens (hit the head \ legs\ body protect the head \ legs\ body). after the battle, return to QuestFragment
class MapLvl fills with the content of FightFragment
I need to change QuestFragment from classLvl to FightFragment. how to do it?
it doesn't work:
class MapLvl.kt:
class MapLevels(){
fun changeLvl (bind: FragmentQuestBinding,hero: Hero, activity: GameActivity,db: Maindb) {
when (hero.mapLvl) {
1 -> MapLevels().mapLevel1(bind, activity, hero, db)
2 -> MapLevels().mapLevel2(bind, activity, hero,db)
else -> {}
}
}
fun mapLevel2 (bind: FragmentQuestBinding,activity: GameActivity,hero:Hero,db: Maindb) {
bind.btnAtack.visibility= View.VISIBLE
//the problem is here:
bind.btnAtack.setOnClickListener {
(activity as GameActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
}
}
}
error: FragmentManager has not been attached to a host
QuestFragment:
class QuestFragment : Fragment() {
lateinit var bind:FragmentQuestBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
bind = FragmentQuestBinding.inflate(inflater)
return bind.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val hero = Hero()
val db = Maindb.heroSetDb(requireActivity())
hero.extractHeroData(db,hero)
scopeMain.launch {
delay(50)
MapLevels().changeLvl(bind,hero,GameActivity(),db)
}
if you make a call directly from a Fragment, then it works: (but it is necessary not from the fragment but from the class)
QuestFragment:
class QuestFragment : Fragment() {
lateinit var bind:FragmentQuestBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
bind = FragmentQuestBinding.inflate(inflater)
return bind.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val hero = Hero()
val db = Maindb.heroSetDb(requireActivity())
hero.extractHeroData(db,hero)
scopeMain.launch {
delay(50)
MapLevels().changeLvl(bind,hero,GameActivity(),db)
}
bind.btnAtack.setOnClickListener {
(activity as GameActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
}
it Work:
class MapLevels(val bind: FragmentQuestBinding,
val hero: Hero,
val db: Maindb,
val activity: FragmentActivity ){
fun changeLvl() {
when (hero.mapLvl) {
1 -> MapLevels(bind,hero,db,activity).mapLevel1()
2 -> MapLevels(bind,hero,db,activity).mapLevel2()
else -> {}
}
}
fun mapLevel2() {
bind.imgLocation.setImageResource(R.drawable.map_loc02)
bind.txtLocationDiscription.text ="text"
bind.btnAtack.visibility= View.VISIBLE
bind.btnAtack.setOnClickListener {
(activity as FragmentActivity).supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder,FightFragment.newInstance())
.commit()
MapLevels().changeLvl(bind,hero,GameActivity(),db)
It looks like you are creating a new GameActivity instance to pass to your changeLvl() method. YOU SHOULD NEVER DO THIS. The new activity is NOT the same one that currently is displayed on the screen. Instead, you should use the requireAcitivty() to get the fragment's parent activity:
MapLevels().changeLvl(bind,hero,requireActivity(),db)
I can't be sure this will fix your current problem because I'm not even sure what the problem is exactly. But it is one issue that you need to change.
I have my MainActivity where there are the movie with their poster. If I click on a poster (in the layout there are 4 ImageView) I can read the plot (a TextView) that is in the Fragment. If I open my app with the emulator MainActivity and Fragment are overlapping so I need to set the ViewModel. How to set it?
//MainActivity
class JacksonActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.jackson_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.jackson_film, JacksonFragment.newInstance())
.commitNow()
}
}
//Fragment
class JacksonFragment : Fragment() {
companion object {
fun newInstance() = JacksonFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return inflater.inflate(R.layout.jackson_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
// TODO: Use the ViewModel
} }
ViewModelProvider is deprecated. more info: ViewModelProvider
To implement viewModel simply use val viewModel: MainViewModel by viewmodels()
if you use the same viewmodel in more than one place(acitity/fragment) in fragment use val viewModel: MainViewModel by activityViewModels()
Note: do not forget to update the dependencies. More info: ViewModel / SharedViewModel
I have a Fragment (InnerFragment) placed inside a Viewpager (MainViewPager).
This Fragment (InnerFragment) does also contain a ViewPager (NestedViewPager) with multiple Fragments. When I swipe or open the InnerFragment everything works fine and the NestedViewPager shows all Fragments the way it should.
When I leave the InnerFragment after swiping the MainViewPager and go back to the InnerFragment it is blank and nothing shows up.
One solution as described in the internet is using the childfragmentmanager. Unfortunately this doesn't work because if I do so following exception is thrown.
java.lang.IllegalStateException: Fragment ProductImageFragment{c458f99 (1213d869-3715-4541-8nab-f87cyc350630) id=0x8f093165 android:switcher:2111xxxxxx:0} declared target fragment ProductImagesFragment{4b4f25e (99a6aaf6-5500-4821-902f-7bf30f87554c) id=0x7f090126 android:switcher:2131296550:2} that does not belong to this FragmentManager!
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:805)
Maybe it is also important to note, that the MainFragment is implementing an Interface of the Innerfragment.
MainFragment
class ProductImagesFragment : Fragment(),
ProductImageFragment.IProductImageHandler,
ProductImageUploadDialog.ProductImageUploadDialogEventListener {
private lateinit var viewPager: ViewPager
interface IProductImageUploadHandler{
fun onImageUploadToServerFinished(bitmap: Bitmap, imageData: ProductImage)
}
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
return layoutInflater.inflate(R.layout.fragment_product_images,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.viewPagerImages)
setViewPagerAdapter()
}
private fun setViewPagerAdapter(){
val manager = fragmentManager
if(manager!=null){
viewPager.adapter = product?.let {product ->
ImageAdapter(this,
product,productImageList,manager)
}
}
}
class ImageAdapter(private val productImagesFragment: ProductImagesFragment, private val product: Product, private val imagesList:ArrayList<ProductImage>, fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
override fun getCount(): Int {
return imagesList.size
}
override fun getItem(position: Int): Fragment {
val fragment = ProductImageFragment()
val bundle = Bundle()
val image = imagesList[position]
bundle.putInt("productId",product.id)
bundle.putParcelable("productImage",image)
bundle.putInt("maxImages",imagesList.size)
fragment.setTargetFragment(productImagesFragment,1)
fragment.arguments = bundle
return fragment
}
}
}
InnerFragment
class ProductImageFragment : Fragment() {
private lateinit var productImageHandler: IProductImageHandler
interface IProductImageHandler{
fun onImageDeleted(id: Int)
fun onOrderChanged(url: String, newValue: Int, oldValue: Int)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
productImageHandler = targetFragment as IProductImageHandler
}catch (exception: Exception){
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_product_image, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
}
private fun setValues(view: View){
}
}
You must use the childFragmentManager in order for the state to be restored properly.
You shouldn't be using the target fragment API at all. In fact, if you're using the childFragmentManager, your child fragments already have a reference to the parent via requireParentFragment() - just use that instead of targetFragment.
override fun onAttach(context: Context) {
super.onAttach(context)
try {
productImageHandler = requireParentFragment() as IProductImageHandler
}catch (exception: Exception){
}
}
I want to create a dialog which contain's ViewPager inside it which have 3 pages and all pages have different layout structure. I want a solution by that i can set the layout content programmatically . I think this can be done by making fragments for each page but i don't know how to do this.
I go through these answers but i am not getting idea how to use them in my case.
Viewpager in Dialog?
ViewPager in Custom Dialog
ViewPager in simple Dialog
You can try and build your custom dialog through DialogFragment. Consider the XML layout would contain a ViewPager and the code to go about would be:
class PagerDialog : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.element_fragment_pager_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPager()
}
private fun setupPager() {
val pagerFragment1 = PagerFragment1.newInstance()
val pagerFragment2 = PagerFragment2.newInstance()
val pagerFragment3 = PagerFragment3.newInstance()
viewPager?.adapter = MyFragmentPagerAdapter(childFragmentManager).apply {
adapterReference = object : PageAdapterInterface {
override var fragmentList: List<Fragment> =
mutableListOf(pagerFragment1, pagerFragment2, pagerFragment3)
}
}
}
companion object {
const val tag = "PagerDialog"
}
}
I have used reference to the list because it might cause leaks when not handled correctly. So the PagerAdapterInterface would look like:
interface PageAdapterInterface {
var fragmentList: List<Fragment>
fun getItemCount() = fragmentList.size
#Throws(StackOverflowError::class)
fun getItemAt(index: Int) : Fragment {
if (index >= fragmentList.size) throw StackOverflowError()
return fragmentList[index]
}
}
Your view pager adapter can make use of this reference in manner that is accessing referentially like:
class MyFragmentPagerAdapter(childFragmentManager: FragmentManager) : FragmentStatePagerAdapter(childFragmentManager){
lateinit var adapterReference: PageAdapterInterface
override fun getItem(p0: Int): Fragment = adapterReference.getItemAt(p0)
override fun getCount(): Int = adapterReference.getItemCount()
}
Finally in your Activity or Fragment on create() or onViewCreated() functions respectively, you can initialize the dialog as shown:
class MyActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// use childFragmentManager if the code is
// used within the Fragment
val prev = supportFragmentManager.findFragmentByTag(PagerDialog.tag)
if (prev != null) {
supportFragmentManager.beginTransaction()
.remove(prev)
.addToBackStack(null)
.commit()
}
PagerDialog().show(supportFragmentManager, PagerDialog.tag)
}
}
Note: DialogFragment is deprecated on > API 28 check out https://developer.android.com/reference/android/app/DialogFragment
I have an activity initializing FragmentA and FragmentA contains FragmentB.
So when I replace FragmentA with some FragmentC, the onPause() and onResume() function of FragmentB is not getting called.
And, when I return back to FragmentA, FragmentB's onPause() is getting called before onResume().
Here is my activity sample code :
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val myFragmentA = FragmentA()
fragmentManager
.beginTransaction()
.replace(R.id.fragContainer, myFragmentA, FRAGMENT_A_TAG)
.commit()
}
}
}
Here is my Fragment sample code :
class FragmentA : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// loading fragmentB
fragmentManager.beginTransaction().replace(R.id.fragBContainer, FragmentB()).commit()
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//my layout changes
}
}
Fragment B : -
class FragmentB : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Todo -- empty
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//my layout changes
}
}
The reason that Fragment B isn't calling it's onResume or onPause() is because it is not being added to the view properly. If you want to nest fragments you need to use getChildFragmentManager() instead of the regular FragmentManager() when managing the nested fragments:
getChildFragmentManager().beginTransaction().replace(R.id.fragBContainer, FragmentB()).commit()