I have a fragment that I would like to use either as a full screen or as a Dialog content.
I'm trying to pass it this way:
companion object {
val DIALOG_TITLE = "DIALOG_TITLE"
fun getInstance(title: String, content: Fragment): BaseCustomDialogFragment {
return BaseCustomDialogFragment().apply {
arguments?.putString(DIALOG_TITLE, title)
activity?.fragmentAdd(content)
}
}
}
Obviously, this solution won't work. As long the Fragment hasn't been attached, we don't have access to the activity.
Is there any way to achieve the same outcome without triggering the load from the Activity?
I found a workaround for this. Instead of passing the Fragment, we include Fragment's class and arguments (Class<T> and Bundle).
Then, we create and add the fragment onViewCreated()
Here is the code:
class BaseCustomDialogFragment: Fragment() {
companion object {
val FRAGMENT_CLASS = "FRAGMENT_CLASS"
val ARGUMENTS_BUNDLE = "ARGUMENTS_BUNDLE"
fun <T> getInstance(title: String, fragmentClass: Class<T>, bundle: Bundle): BaseCustomDialogFragment {
return BaseCustomDialogFragment().apply {
arguments = Bundle()
arguments!!.putString(DIALOG_TITLE, title)
arguments!!.putSerializable(FRAGMENT_CLASS, fragmentClass)
arguments!!.putBundle(ARGUMENTS_BUNDLE, bundle)
}
}
}
private lateinit var className: Class<*>
private lateinit var bundle: Bundle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
(arguments?.getSerializable(FRAGMENT_CLASS) as? Class<*>)?.let { className = it }
arguments?.getBundle(ARGUMENTS_BUNDLE)?.let { bundle = it }
// Inflate View.
return inflater.inflate(R.layout.fragment_custom_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setContentUp()
}
private fun setContentUp(){
val fragment = createFragment(className, bundle)
activity?.supportFragmentManager
?.beginTransaction()
?.add(R.id.fragment_dialog_placeholder, fragment)
?.disallowAddToBackStack()
?.commit()
}
private fun <T> createFragment(fragmentClass: Class<T>, arguments: Bundle): Fragment {
val fragment = fragmentClass.newInstance() as Fragment
fragment.arguments = arguments
return fragment
}
I hope it helps to somebody.
Related
I have two fragments and want to open second fragment (WordFragment) when pressing button of the first one(SearchFragment) by Bundle. But Fragment shwos default data instead of the passed one.
ClickListener in First Fragment:
searchDefAdapter = SearchDefAdapter(
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val bundle = Bundle()
val arr = arrayOf("word", "слово", "I give you my word")
bundle.putStringArray(INFO_BUNDLE_ID, arr)
val wordFragment = WordFragment()
wordFragment.arguments = bundle
parentFragmentManager.beginTransaction().apply {
replace(R.id.searchFragment, WordFragment())
commit()
}
}
},
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
//viewModel.saveWord(position)
}
}
)
Second Fragment:
class WordFragment : Fragment() {
private var _binding: FragmentWordBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentWordBinding.inflate(inflater, container, false)
val view = binding.root
arguments?.let {
val translation = it.getStringArray(INFO_BUNDLE_ID)
if (translation != null){
binding.wordTitleTv.text = translation[0]
binding.translationTv.text = translation[1]
binding.exampleTv.text = translation[2]
}
}
return view
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
this is because you create new WordFragment here replace(R.id.searchFragment, WordFragment()) instead of putting the one you added the bundle to it.
I need to get session id to menu fragment. but it doesn't work. In this I get Session ID from Login Activity. Now I need to get that id to Menu fragment. In this one activity loaded five Fragments. I used bundle for load data.
Here's my activity.kt
class Home : AppCompatActivity() {
private val HomeFragment = HomeFragment()
private val CreditsFragment = CreditsFragment()
private val BusFragment = BusFragment()
private val NotificationsFragment = NotificationsFragment()
private val MenuFragment = MenuFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home)
val sessionId = intent.getStringExtra("EXTRA_SESSION_ID")
textView4.setText(sessionId)
replaceFragment(HomeFragment)
bottomNavBar.setOnNavigationItemSelectedListener{
when (it.itemId){
R.id.menu_home -> replaceFragment(HomeFragment)
R.id.menu_credits -> replaceFragment(CreditsFragment)
R.id.menu_bus -> replaceFragment(BusFragment)
R.id.menu_notification -> replaceFragment(NotificationsFragment)
R.id.menu_menu -> replaceFragment(MenuFragment)
}
true
}
val bundle = Bundle()
bundle.putString("EXTRA_SESSION_ID", sessionId)
val myObj = MenuFragment()
myObj.setArguments(bundle)
}
private fun replaceFragment (fragment:Fragment){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
here's my updated menu fragment.kt. I think I have problem in this. can u plzzz help me to find it.
class MenuFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
var sessionId = it.toString()
emailAddressText.setText(sessionId)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_menu, container, false)
}
companion object {
}
}
change activity to this :
val bundle = Bundle()
bundle.putString("EXTRA_SESSION_ID", sessionId)
MenuFragment.setArguments(bundle)
bottomNavBar.setOnNavigationItemSelectedListener{
when (it.itemId){
R.id.menu_home -> replaceFragment(HomeFragment)
R.id.menu_credits -> replaceFragment(CreditsFragment)
R.id.menu_bus -> replaceFragment(BusFragment)
R.id.menu_notification -> replaceFragment(NotificationsFragment)
R.id.menu_menu -> replaceFragment(MenuFragment)
}
true
}
in the fragment you also need to use arguments.getString("EXTRA_SESSION_ID") to retreive sessionId value. and place emailAddressText.setText(sessionId)
in onViewCreated method because in onCreate() method views are not ready yet.
so change the fragment like this :
class MenuFragment : Fragment() {
var sessionId = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
sessionId = it.getString("EXTRA_SESSION_ID","")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_menu, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
emailAddressText.setText(sessionId)
}
}
The MenuFragment instance you have in your replaceFragment transaction is different from the one where you set the sessionid argument to.
Remove the other instantiation val myObj = MenuFragment() and set arguments on MenuFragment instead.
(Naming convention hint: it's easier to keep types and objects apart if types are CamelCase and types lowerCamelCase. For example, private val menuFragment = MenuFragment().)
I'm trying to write a data transfer from an activity to a fragment, but I'm catching "NullPointerException"
Code MainActivity:
val btn1 = findViewById<Button>(R.id.button)
btn1.setOnClickListener {
BlankFragment2.getNewInstance(321)
val fm = supportFragmentManager
val ft = fm.beginTransaction()
ft.replace(R.id.fragment, BlankFragment2())
ft.commit()
}
Code Fragment:
class BlankFragment2 : Fragment() {
var str: Int = 0
companion object {
fun getNewInstance(args: Int): BlankFragment2 {
val fragment = BlankFragment2()
fragment.arguments?.putInt("one", args)
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
str = arguments!!.getInt("one")
return inflater.inflate(R.layout.fragment_blank2, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val txt = view.findViewById<TextView>(R.id.textTest)
txt.text = str.toString()
}
}
I know about protection against null-values, and the problem is that I am sure that the value is passed and null cannot be there
instead of
ft.replace(R.id.fragment, BlankFragment2()
you should use
ft.replace(R.id.fragment, BlankFragment2.getNewInstance(321).
Because your BlankFragment2.getNewInstance(321) statement above is kind of useless to the fragmentManager. FragmentManager is creating fragment using BlankFragment2() as you provided the Fragment in the replace call.
And that is the reason for nullpointerexception because in reality, your Fragment didn't get any int value at all as the Fragment instance used was created with the empty constructor.
And also update your companion object code like below.
companion object {
fun getNewInstance(args: Int): BlankFragment2 {
val fragment = BlankFragment2()
val args = Bundle()
args.putInt("one", args)
fragment.setArguments(args)
return fragment
}
}
because right now your code is actually not setting argument but try to access argument and setting value to it.
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){
}
}
class UserFragment : Fragment(), View.OnClickListener {
private val userBinding: FragmentUserBinding by onCreateView<Fragment, FragmentUserBinding>(R.layout.fragment_user)
val user: ObservableField<String> = ObservableField()
private var bundle = Bundle()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userBinding.main = this
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = userBinding.root!!
override fun onClick(p0: View?) {
runAnimation(500L, Techniques.RubberBand, p0)
Handler().postDelayed({
saveUserAndStartLetterFragment()
}, 700L)
}
private fun saveUserAndStartLetterFragment() {
var fragment = WordpackChooserFragment()
bundle.putString("User",user.get())
fragment.arguments = bundle
activity!!.supportFragmentManager.replaceFragment(fragment, activity!!.findViewById(R.id.flContainer))
}
}
In this fragment I declare the bundle. I do not specify it as nullable.
class WordpackChooserFragment : Fragment(), View.OnClickListener {
private val wordpackChooserBinding: FragmentWordpackChooserBinding by onCreateView<Fragment, FragmentWordpackChooserBinding>(R.layout.fragment_wordpack_chooser)
private var bundle: Bundle = Bundle()
private lateinit var fragment: Fragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
wordpackChooserBinding.main = this
setRecycler()
bundle = arguments
return wordpackChooserBinding.root
}
override fun onClick(v: View?) {
fragment = RoundsChooserFragment()
bundle.putStringArrayList("Wordpack", selectedPack)
fragment.arguments = bundle
activity!!.supportFragmentManager.replaceFragment(fragment, activity!!.findViewById(R.id.flContainer))
}
private fun setRecycler() {
wordpackChooserBinding.root.btn_recycler.layoutManager = LinearLayoutManager(this.context)
wordpackChooserBinding.root.btn_recycler.adapter = BtnAdapter(this)
wordpackChooserBinding.root.btn_recycler.adapter.notifyDataSetChanged()
}
}
In this fragment this line bundle = arguments errors with the following:
Type mismatch.
Required: Bundle
Found: Bundle?
I can use the non-null assertion operator '!!' but It seems like a hack.
arguments is nullable (note the "if any" in the description of the link), therefore you can not simply assign it to a non-nullable Bundle. You would either handle the case of it being null (using an if), using !! or you could write something like this:
arguments?.let { bundle = it }
However, I'd say its preferred to use let.