In an android app I have an activity with several fragments. In one of these fragments I have an ImageButton. On click I want to let something happen in the activity directly, not in the fragment. In this case I want to set an image. Summed up, the ImageButton is in the fragment, but the ImageView I want to change on click is in the activity.
How do I achieve that? Whatever I've tried resulted in an app crash.
This is how my fragment.kt looks:
class Fragment1 : Fragment() {
companion object{
fun newInstance() = Fragment1()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_1, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageButton.setOnClickListener{
imageView.setImageResource(R.drawable.example)
}
}
}
class Fragment1 : Fragment() {
var callbacks: OnFragmentCallbacks? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
callbacks = activity as OnFragmentCallbacks
}
interface OnFragmentCallbacks{
fun changeImage(resourceId: Int)
}
companion object{
fun newInstance() = Fragment1()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_1, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageButton.setOnClickListener{
callbacks?.changeImage(R.drawable.example)
}
}
In your Activity:
// Change FragmentName with name of your Fragment class
class MainActivity : AppCompatActivity(), FragmentName.OnFragmentCallbacks{
override fun changeImage(resouseId: Int){
imageView.setImageDrawable(getResources().getDrawable(resourceId))
// or
imageView.setImageResource(resourceId)
}
Just coded it in stackoverflow, may not be perfect
Related
I struggle to do a property delegate for ViewBinding on BottomSheets.
The general Idea is similar to this
For Fragments i use something like this
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory)
On BottomSheetsDialogFragments which are Fragments it does not accept the delegate.
fun <T : ViewBinding> BottomSheetDialogFragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory)
The Lifecycle of BottomSheets would be the same as on regular Fragments, so i would not expect any problems.
Anyone came up with a solution on this ?
You can still use the FragmentViewBindingDelegate and extension from Gabor.
You just also need to inflate the view inside onCreateView().
For example:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.zhuinden.fragmentviewbindingdelegatekt.viewBinding
class ExampleBottomSheet : BottomSheetDialogFragment() {
//Using ::bind here since the view is already inflated in onCreateView()
private val binding by viewBinding(YourCustomViewBinding::bind)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.your_custom_view, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Use binding here or wherever you need it
}
}
Implement ViewBinding in BottomSheetFragment like this (Works for me):
class CustomBottomSheet : BottomSheetDialogFragment() {
private lateinit var binding: CustomBottomSheetBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = CustomBottomSheetBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//onClick listener
binding.button.setOnClickListener {
Toast.makeText(context, "Clicked", Toast.LENGTH_LONG).show()
}
}
}
This works:
class CampaignBottomSheet : BottomSheetDialogFragment() {
private var _binding: MenuCampaignsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = MenuCampaignsBinding.inflate(layoutInflater)
binding.startCampaign.setOnClickListener { println("__print::CampaignBottomSheet") }
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Unlike Fragment class, BottomSheetDialogFragment doesn't have a constructor that accepts layout resource.
You can create a custom dialog for it. Basically, copy everything from AppCompatDialogFragment and BottomSheetDialogFragment.
Guys I need your help.
I use android navigation component and want to save backstack after user press button and restore it after. I found 2 methods
navController.saveState(): Bundle and navController.restoreState(bundle: Bundle).
But i have problem in use it. Seems like saveState work greate (i see bundle, and backstack inside), but i dont understand how to use restoreState, because the documentation says:
Restores all navigation controller state from a bundle. This should be called before any call to setGraph.
https://developer.android.com/reference/kotlin/androidx/navigation/NavController#restorestate
Okay, i did it, seems like backstack restored, but on screen i see first fragment (instead of the one I had when I saved it). What i do wrong?
Code:
FirstFragment
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_forward.setOnClickListener { findNavController().navigate(R.id.action_firstFragment_to_secondFragment) }
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
SecondFragment
class SecondFragment : Fragment() {
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_forward.setOnClickListener { findNavController().navigate(R.id.action_secondFragment_to_thirdFragment) }
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
ThirdFragment
class ThirdFragment : Fragment() {
private val TAG = this::class.java.name
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_third, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btn_finish.setOnClickListener {
(requireActivity() as MainActivity).saveState() //here save bundle
requireActivity().finishAfterTransition()
}
btn_back.setOnClickListener { requireActivity().onBackPressed() }
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val TAG = "MySuperActivity"
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate($savedInstanceState) called")
initNavController()
}
private fun initNavController() {
val navHostFragment = nav_host_fragment as NavHostFragment
val graphInflater = navHostFragment.navController.navInflater
val graph = graphInflater.inflate(R.navigation.main_graph)
navController = navHostFragment.navController
navHostFragment.childFragmentManager
if (App.instance.savedBundle != null) {
Log.d(TAG, "bundle: ${App.instance.savedBundle}")
navController.restoreState(App.instance.savedBundle)
graph.startDestination = R.id.thirdFragment
}
navController.graph = graph
Log.d(TAG, "navController.currentDestination: ${navController.currentDestination}")
Log.d(TAG, "navController.graph.startDestination: ${navController.graph.startDestination}")
}
fun saveState(){
App.instance.savedBundle = navController.saveState()
Log.d(TAG, "saveState() : ${App.instance.savedBundle}")
}
}
here some logs: logs
full code:github
I am not sure if my answer helps you, but I had many issues trying to save the navigation state from handling rotations. The issue that I had comes from an old version of the navigation component, I update to the most recent, and it fixes the issue:
def android_navigation = '2.3.4'
implementation "android.arch.navigation:navigation-fragment-ktx:$android_navigation"
implementation "android.arch.navigation:navigation-ui-ktx:$android_navigation"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$android_navigation"
I'm having problems that all my fragments are called when the application start so when I do toast on the the second fragment it appear on the first fragment
here is my activity code
class HomePage : AppCompatActivity() {
var adapter: FPA? = null
val manager = supportFragmentManager
private lateinit var drawer: DrawerLayout
private lateinit var toggle: ActionBarDrawerToggle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(null)
setContentView(R.layout.home_page_act)
adapter = FPA(supportFragmentManager)
container.adapter = adapter
container.addOnPageChangeListener(android.support.design.widget.TabLayout.TabLayoutOnPageChangeListener(tabs))
tabs.addOnTabSelectedListener(android.support.design.widget.TabLayout.ViewPagerOnTabSelectedListener(container))
}
class FPA(fm: FragmentManager) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
if(position==0 )
return TrainingForSale()
else if(position==1)
return Shops()
else
return Health()
}
override fun getCount(): Int {
return 3
}
}
fun ShowFramentShops (){
val transaction = manager.beginTransaction ()
val fragmet = Shops()
transaction.replace(R.id.container,fragmet)
transaction.addToBackStack(null)
transaction.commit()
}
}
and here is my fragment pages
fragment (1)
class Shops : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.shops_fragment, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onCreate(null);
//UserInformation.TrainingFilter=""
//UserInformation.TrainingList.clear()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(null)
Toast.makeText(activity,"Shopes",Toast.LENGTH_LONG).show()
}
}// Required empty public constructor
my second fragment
class Health : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.health_fragment, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onCreate(null);
super.onViewCreated(view, savedInstanceState)
Toast.makeText(activity,"Health", Toast.LENGTH_LONG).show()
}
}// Required empty public constructor
So when I run the the app both toast appears in the same page
The ViewPager will not only create the current page, but also the some before or after. With setOffscreenPageLimit(int) you can adapt these values a little.
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
I have a simply Activity. In this activity I want start a fragment. But the Fragment doesn't start.
This is my Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//start Homefragment
startHomeFragment()
}
/**
* Create Homefragment
*/
private fun startHomeFragment() {
val homeFragment = fragmentManager.findFragmentByTag(statics.TAG) ?: HomeFragment()
fragmentManager.beginTransaction()
.replace(R.id.container, homeFragment, statics.TAG)
.commit()
}
And this is my Fragment
class HomeFragment : Fragment() {
object statics {
#JvmField val TAG = "Homefragment"
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
inflater?.inflate(R.layout.fragment_home, container)
background_home.setBackgroundResource(R.drawable.gc_bg_home)
All the code after
return super.onCreateView(inflater, container, savedInstanceState)
is unreachable. Just read IDE warnings.
Also, layout becomes attached only if you returned it from onCreateView, i. e. return inflater.inflate(...).
In Kotlin the onCreateView should be on this way
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.input_field_frag, container, false)
}
If you want to add somthing inside you fragment you can use onViewCreated method and do whatever you want here
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Here u can call function of add listners to button...
})
}
Like this you gonna create a safe propriate code
Solved:
class HomeFragment : Fragment() {
object statics {
#JvmField val TAG = "Homefragment"
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Toast.makeText(activity, "HomeFragment", Toast.LENGTH_SHORT).show()
return inflater?.inflate(R.layout.fragment_home, container, false)
}
}
class HomeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val v = inflater.inflate(R.layout.fragment_home, container, false)
val fab = v.findViewById(R.id.fab) as FloatingActionButton
fab.setOnClickListener {
val blankFragment = BlankFragment()
val manager = childFragmentManager
manager.beginTransaction().replace(R.id.frame_container, blankFragment, blankFragment.tag).commit()
// System.out.println("You have reached the floating action button")
}
return v
}
}
Getting a no view found error. I may have issues with the R.id.frame_content but Kotlin doesn't immediately identify all id values...
This may not be the best way and I apologize for the horrible formatting I does this while answer out on my phone and it's hard to get it perfect. Anyways In the activity that holds your fragments, such as MainActivity.kt add...
supportFragmentManager.beginTransaction.add(R.id.fragment_home, HomeFragment.newInstance, "HomeFragment").commit()
In your HomeFragment.kt change the following.
class HomeFragment: Fragment(){
companion object {
fun newInstance(): HomeFragment{
return HomeFragment() }
fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_home, container, false)
val activity = activity
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val clickListener = View.OnClickListener { view ->
when (view.getId()) {
R.id.fab -> NextFragment()
}
}
fab.setOnClickListener(clickListener)
}
NextFragment(){
val fragManager = activity?.supportFragmentManager
fragManager?.beginTransaction()?.replace(R.id.frame_container, BlankFragment.newInstance(), "blankFragment")?.commit()
}
Make sure you make the same changes to BlankFragment.kt
class BlankFragment: Fragment(){
companion object {
fun newInstance(): BlankFragment{
return BlankFragment() }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_blank, container, false)
val activity = activity
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Do some stuff with your views in BlankFragment here
}
}