Got a class that extends BottomSheetDialogFragment
When I set the dialog to show, the slide from bottom animation occurs and the dialog is shown. Now, If I set the app to background and then bring it back to the foreground, the dialog that was already showing does the same slide in animation.
How can I disable this, that is, if the dialog is already showing, sending the app to background and then foreground does not start the animation?
Showing the dialog like this:
dialog = MyDialogFragment()
dialog?.run {
val args = Bundle()
....
arguments = args
show(this#MyFragment.fragmentManager, tag)
}
Where the dialog class only has this:
class MyDialogFragment : BottomSheetDialogFragment() {
var listener: Listener? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// inflate view
// Given the arguments, sets up the ui
return view
}
interface Listener {
...
}
}
And that's all the code that I have for the dialog.
Combining #nntk and #CKotlin answers made the trick for me. I manage to fix the issue with this code :
override fun onStop() {
super.onStop()
dialog?.window?.setWindowAnimations(-1)
}
Edit:
On Android < P (API 28), it makes the app not responding to touch events. So either you don't use it or you need to surround it with a check:
override fun onStop() {
super.onStop()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
dialog?.window?.setWindowAnimations(-1)
}
}
overwrite onStop() method in BottomSheetDialogFragment
such as
#Override
public void onStop() {
super.onStop();
getDialog().show(); // important
}
I'm dealing with the same issue. I haven't fully been able to solve this, but I was able to stop the slide up animation from occurring on resume by doing the following:
override fun onResume() {
super.onResume()
// Disable dialog window animations for this instance
dialog?.window?.setWindowAnimations(-1)
}
This doesn't stop the background fade from animating though, so the background shade shows, disappears, and then shows again.
Related
I open a new bottomSheet from a bottomSheet. When service result is successful I call dismiss() and open new fragment. This works most of times but sometimes previous bottomSheet now dismissing. For example when an alertDialog is shown and I close bottom sheet then reopen it and this issue is occurring.
First BottomSheet:
btnSend.click {
viewModel.callServiceFunction()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
observe(viewModel.serviceSuccessLiveData) {
dismiss()
navigator.navigateToSecondSheet(parentFragmentManager)
}
}
navigateToSecondSheet(fm: FragmentManager) {
SecondSheet.show(fm)
}
Second Bottom Sheet:
companion object {
fun show(fm: FragmentManager) {
SecondSheet().show(fm, "TagA")
}
}
So how can I get the first bottom sheet to always be dismissed?
maybe use:
dismissAllowingStateLoss()
I'm using following approach to create bottomsheet dialog, dialog is creating but I want to dismiss on backpress for that all setup I'm using the code below. But not doing able to dismiss dialog.
class MainActivity : BaseClass(), View.OnClickListener {
private lateinit var bottomSheetDialog: BottomSheetDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottomSheetDialog=BottomSheetDialog(this)
create.setOnClickListener {
createBottomSheetDialog()
}
}
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
}
super.onBackPressed()
}
private fun createBottomSheetDialog(){
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog)
bottomSheetDialog.setCancelable(false)
bottomSheetDialog.show()
}
}
I have been tried using this.bottomSheetDialog.dismiss()
but not working I also have try dismissing the dialog without if statement and without using super.onBackPressed() but not working.
you can simply remove this line from your code
bottomSheetDialog.setCancelable(false)
You can get more details about this here. I guess you need to stop dismiss when clicked anywhere outside the fragment so you can add this :-
bottomSheetDialog.setCanceledOnTouchOutside(false)
It will allow you to dismiss onBackPressed but it won't allow you to dismiss if clicked anywhere outside of your bottom sheet.
Also no need to write this in your Main activity :-
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
}
super.onBackPressed()
}
Bottom Sheet dialogue will itself dismiss onBackPressed.
Try this:
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
} else {
super.onBackPressed()
}
}
Try setting global variable for activity dialog: BottomSheetDialog? = null
Then in the function for displaying dialog give it a value and show it. Then in onCreate (or whenever you want) just use global variable as a reference eg.:
onBackPressed(){
if(dialog != null){
dialog.dismiss()}}
edit: 2020.4.12 correct typo from Button.performClick() to button.performClick()
I am writing an app which should display a splash page/fragment for a
few seconds at start then display the next fragment in the navgraph. There are seven fragments in the navgraph which I can navigate around those fragments just fine.
The issue is with the splash fragment, I can only get the splash fragment to display/inflate when the button.onClickListener is set to accept a manual user
input -> click. (vs using button.performClick())
The desired end result is to display a fragment layout consisting of an image view and a text view for a few seconds at app start before displaying the next fragment layout in the navgraph, without having the user to click or press anything.
I have tried using threadsleep, a runnable with a handler, and even a while loop with performClick(). None of which have yielded acceptable results. The closest I have come to getting the desired result is the following:
class SplashFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater,
rootContainer: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflater(R.layout.fragment_splash, rootContainer, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button.setOnClickListener {
updateABode()
}
// pizzaLoop initialized to give about 4 seconds delay
val pizzaLoop = 1500000000
while (pizzaLoop > 0) {
pizzaLoop--
if (pizzaLoop == 0) {
button.performClick()
}
}
private fun updateABode {
val ABode = "A" // hard coded for testing purposes
when (ABode) {
"B" -> // for testing purposes only -- does nothing
"A" -> findNavController().navigate(R.id.action_splashFragment_to_firmwareFragment)
}
}
}
With the pizzaLoop installed, the splash fragment will not inflate, but I do see the delay via the firmware screen update. (intially all I get is a white blank screen then subsequent calls to the SplashFragment class show nothing but the firmwareFragment screen (next in the navgraph) -- and the pizzaLoop delay is noticable).
When I comment out the pizzaLoop then the splash fragment displays as intended but I have to click the button to bring up the next fragment in the navgraph (the rest of the navgraph works fine).
It's like the button.performClick() method is preventing the inflation of the splash fragment.
EDIT: 2020.4.12 TO PROPERLY POST SOLUTION.
class SplashFragment : Fragment() {
private val handler: Handler = Handler()
private val updateRunnable: Runnable = Runnable { updateABode() }
override fun onCreateView(inflater: LayoutInflater,
rootContainer: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflater(R.layout.fragment_splash, rootContainer, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button.setOnClickListener {
handler.removeCallbacks(updateRunnable)
}
handler.postDelayed(updateRunnable, 4000)
}
private fun updateABode {
val ABode = "A" // hard coded for testing purposes
when (ABode) {
"B" -> // for testing purposes only -- does nothing
"A" -> findNavController().navigate(R.id.action_splashFragment_to_firmwareFragment)
}
}
}
I have an activity using fragments. To communicate from the fragment to the activity, I use interfaces. Here is the simplified code:
Activity:
class HomeActivity : AppCompatActivity(), DiaryFragment.IAddEntryClickedListener, DiaryFragment.IDeleteClickedListener {
override fun onAddEntryClicked() {
//DO something
}
override fun onEntryDeleteClicked(isDeleteSet: Boolean) {
//Do something
}
private val diaryFragment: DiaryFragment = DiaryFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
diaryFragment.setOnEntryClickedListener(this)
diaryFragment.setOnDeleteClickedListener(this)
supportFragmentManager.beginTransaction().replace(R.id.content_frame, diaryFragment)
}
}
The fragment:
class DiaryFragment: Fragment() {
private var onEntryClickedListener: IAddEntryClickedListener? = null
private var onDeleteClickedListener: IDeleteClickedListener? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_diary, container, false)
//Some user interaction
onDeleteClickedListener!!.onEntryDeleteClicked()
onDeleteClickedListener!!.onEntryDeleteClicked()
return view
}
interface IAddEntryClickedListener {
fun onAddEntryClicked()
}
interface IDeleteClickedListener {
fun onEntryDeleteClicked()
}
fun setOnEntryClickedListener(listener: IAddEntryClickedListener) {
onEntryClickedListener = listener
}
fun setOnDeleteClickedListener(listener: IDeleteClickedListener) {
onDeleteClickedListener = listener
}
}
This works, but when the fragment is active and the orientation changes from portrait to landscape or otherwise, the listeners are null. I can't put them to the savedInstanceState, or can I somehow? Or is there another way to solve that problem?
Your Problem:
When you switch orientation, the system saves and restores the state of fragments for you. However, you are not accounting for this in your code and you are actually ending up with two (!!) instances of the fragment - one that the system restores (WITHOUT the listeners) and the one you create yourself. When you observe that the fragment's listeners are null, it's because the instance that has been restored for you has not has its listeners reset.
The Solution
First, read the docs on how you should structure your code.
Then update your code to something like this:
class HomeActivity : AppCompatActivity(), DiaryFragment.IAddEntryClickedListener, DiaryFragment.IDeleteClickedListener {
override fun onAddEntryClicked() {
//DO something
}
override fun onEntryDeleteClicked(isDeleteSet: Boolean) {
//Do something
}
// DO NOT create new instance - only if starting from scratch
private lateinit val diaryFragment: DiaryFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
// Null state bundle means fresh activity - create the fragment
if (savedInstanceState == null) {
diaryFragment = DiaryFragment()
supportFragmentManager.beginTransaction().replace(R.id.content_frame, diaryFragment)
}
else { // We are being restarted from state - the system will have
// restored the fragment for us, just find the reference
diaryFragment = supportFragmentManager().findFragment(R.id.content_frame)
}
// Now you can access the ONE fragment and set the listener on it
diaryFragment.setOnEntryClickedListener(this)
diaryFragment.setOnDeleteClickedListener(this)
}
}
Hope that helps!
the short answer without you rewriting your code is you have to restore listeners on activiy resume, and you "should" remove them when you detect activity losing focus. The activity view is completely destroyed and redrawn on rotate so naturally there will be no events on brand new objects.
When you rotate, "onDestroy" is called before anything else happens. When it's being rebuilt, "onCreate" is called. (see https://developer.android.com/guide/topics/resources/runtime-changes)
One of the reasons it's done this way is there is nothing forcing you to even use the same layout after rotating. There could be different controls.
All you really need to do is make sure that your event hooks are assigned in OnCreate.
See this question's answers for an example of event assigning in oncreate.
onSaveInstanceState not working
My bottomSheet behaves correctly except in this situation. When I return to the activity via 'back button', I want the bottomSheet to collapse and I thought the code below would do the trick, but it doesn't work. What could be the cause ? (I confirmed with debugger that it reaches the statement)
#Override
public void onBackPressed() {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
the same line works fine when it returns via finish():
if (resultCode == Activity.RESULT_OK) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
If your dialog has setCancelable(true), back button will not trigger the onbackpressed(), you can try this
I have a class call BottomSheetFragmentDialog which is extend from BottomSheetDialogFragment and I have setCanceledOnTouchOutside(false) inside the onCreateDialog Method also overrdie the onCancel()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setCanceledOnTouchOutside(false)
return dialog
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
Toast.makeText(context, "Break Point Here", Toast.LENGTH_SHORT).show()
}
you can use this code onBackPressed() methode
behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
Simpler solution.
Don't have to override onBackPressed method, just have to remove setCancelable(true) and add bottomSheetDialog.setCanceledOnTouchOutside(false) when you initialise the bottomSheetDialog.