Implement onBackPressed for 2 fragments - android

I've looked at multiple questions, haven't found solution yet.
I've youtube player SDK imported and a webview. One is in Live_fragment and another Article_Fragment.
This is the onBackPressed code that causes the crash:
#Override
override fun onBackPressed() {
if (webViewE.canGoBack() || youTubePlayer.isFullScreen()) {
youTubePlayer.exitFullScreen()
webViewE.goBack()
} else {
super.onBackPressed()
}
}
Now, webViewE.canGoBack and youTubePlayer.isFullScreen() alone works fine (if I only put in one of them), but if they're both together in the function, I get:
Attempt to invoke virtual method 'boolean android.webkit.WebView.canGoBack()' on a null object reference" and app crashes

This means your WebView variable is null at the time of the onBackPressed invocation. You need to make sure you properly initialise it and ensure it hasn't been nullified somehow up until possible calls to onBackPressed. If you want to axe this condition in the event that the WebView may be null, you can just add a check for null alongside your current check, i.e.:
#Override
override fun onBackPressed() {
if ((webViewE != null && webViewE.canGoBack()) || youTubePlayer.isFullScreen()) {
youTubePlayer.exitFullScreen()
webViewE.goBack()
} else {
super.onBackPressed()
}
}

Related

List find method finds nothing, but let is called (?!)

i have the following code:
myList.find { it.code == item.bin }.let {
// 1
} ?: run {
// 2
}
I would expect that, if item is found, I enter block 1 , otherwise block 2 ;
But instead I enter block 1 in all cases, and if nothing is found , it is null
Android studio seems aware of this, as the block 2 is grey (it is code never called), but I can't figure why
Please may someone explain why ?
null is still a value so it makes sense that the first block is run. it'd be the same as if you ran null.let { println("hey!") }.
you probably want to run let with a null check: myList.find { it.code == item.bin }?.let { ... }. this way the block will only run if there is indeed a value being returned that is not null.
You are using classic dot call operator ., this operator is not allowed on nullable types. If you want to call this operator on nullable type insert !! before operator, but if you call it on null it throws NullPointerException
You have to use Kotlins safe call operator ?., which call method when insatce is not null and when is it returns null.
?: operator is called Elvis operator and it returns first value if it is not null, else it returns second value.
So just change in your code dot operator . to safe call operator ?.:
myList.find { it.code == item.bin }.let {
// 1
} ?: run {
// 2
}

How to implement back button handling on Android with Redux

I'm trying to implement back button handling on Android using CoRedux for my Redux store. I did find one way to do it, but I am hoping there is a more elegant solution because mine seems like a hack.
Problem
At the heart of the problem is the fact returning to an Android Fragment is not the same as rendering that Fragment for the first time.
The first time a user visits the Fragment, I render it with the FragmentManager as a transaction, adding a back stack entry for the "main" screen
fragmentManager?.beginTransaction()
?.add(R.id.myFragmentContainer, MyFragment1())
?.addToBackStack("main")?.commit()
When the user returns to that fragment from another fragment, the way to get back to it is to pop the back stack:
fragmentManager?.popBackStack()
This seems to conflict with Redux principles wherein the state should be enough to render the UI but in this case the path TO the state also matters.
Hack Solution
I'm hoping someone can improve on this solution, but I managed to solve this problem by introducing some state that resides outside of Redux, a boolean called skipRendering. You could call this "ephemeral" state perhaps. Initialized to false, skipRendering gets set to true when the user taps the back button:
fun popBackStack() {
fragmentManager?.popBackStack()
mapViewModel.dispatchAction(MapViewModel.ReduxAction.BackButton)
skipRendering = true
}
Dispatching the back button to the redux store rewinds the redux state to the prior state as follows:
return when (action) {
// ...
ReduxAction.BackButton -> {
state.pastState
?: throw IllegalStateException("More back taps processed than past state frames")
}
}
For what it's worth, pastState gets populated by the reducer whenever the user requests to visit a fragment from which the user can subsequently tap back.
return when (action) {
// ...
ReduxAction.ShowMyFragment1 -> {
state.copy(pastState = state, screenDisplayed = C)
}
}
Finally, the render skips processing if skipRendering since the necessary work of calling fragmentManager?.popBackStack() was handled before dispatching the BackButton action.
I suspect there is a better solution which uses Redux constructs for example a side effect. But I'm stuck figuring out a way to solve this more elegantly.
To solve this problem, I decided to accept that the conflict cannot be resolved directly. The conflict is between Redux and Android's native back button handling because Redux needs to be master of the state but Android holds the back stack information. Recognizing that these two don't mix well, I decided to ditch Android's back stack handling and implement it entirely on my Redux store.
data class LLReduxState(
// ...
val screenBackStack: List<ScreenDisplayed> = listOf(ScreenDisplayed.MainScreen)
)
sealed class ScreenDisplayed {
object MainScreen : ScreenDisplayed()
object AScreen : ScreenDisplayed()
object BScreen : ScreenDisplayed()
object CScreen : ScreenDisplayed()
}
Here's what the reducer looks like:
private fun reducer(state: LLReduxState, action: ReduxAction): LLReduxState {
return when (action) {
// ...
ReduxAction.BackButton -> {
state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
it.addAll(state.screenBackStack)
it.removeAt(0)
})
}
ReduxAction.AButton -> {
state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
it.add(ScreenDisplayed.AScreen)
it.addAll(state.screenBackStack)
})
}
ReduxAction.BButton -> {
state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
it.add(ScreenDisplayed.BScreen)
it.addAll(state.screenBackStack)
})
}
ReduxAction.CButton -> {
state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
it.add(ScreenDisplayed.CScreen)
it.addAll(state.screenBackStack)
})
}
}
}
In my fragment, the Activity can call this API I exposed when the Activity's onBackPressed() gets called by the operating system:
fun popBackStack() {
mapViewModel.dispatchAction(MapViewModel.ReduxAction.BackButton)
}
Lastly, the Fragment renders as follows:
private fun render(state: LLReduxState) {
// ...
if (ScreenDisplayed.AScreen == state.screenBackStack[0]) {
fragmentManager?.beginTransaction()
?.replace(R.id.llNavigationFragmentContainer, AFragment())
?.commit()
}
if (ScreenDisplayed.BScreen == state.screenBackStack[0]) {
fragmentManager?.beginTransaction()
?.replace(R.id.llNavigationFragmentContainer, BFragment())
?.commit()
}
if (ScreenDisplayed.CScreen == state.screenBackStack[0]) {
fragmentManager?.beginTransaction()
?.replace(R.id.llNavigationFragmentContainer, CFragment())
?.commit()
}
}
This solution works perfectly for back button handling because it applies Redux in the way it was meant to be applied. As evidence, I was able to write automation tests which mock the back stack as follows by setting the initial state to one with the deepest back stack:
LLReduxState(
screenBackStack = listOf(
ScreenDisplayed.CScreen,
ScreenDisplayed.BScreen,
ScreenDisplayed.AScreen,
ScreenDisplayed.MainScreen
)
)
I've left some details out which are specific to CoRedux.

Kotlin - Clear Button listener for google's Place Autocomplete API onClick not called

I'm trying to create a listener for the clear button that comes from google's Place Autocomplete API. i called my clearButton() method in my fragment's onViewCreated method
clearButton()
placeAutocompleteFragment?.view?.findViewById<View>(R.id.place_autocomplete_clear_button)
?.setOnClickListener {
View.OnClickListener {
Log.d(TAG, "Cleared")
it?.findViewById<EditText>(R.id.place_autocomplete_search_input)?.setText("")
it?.visibility = View.GONE
}
}
now when i click on the clear button icon, the text doesn't get erased, nothing happens. I can still type in a new location though, but i can't clear it. my Log.d isn't getting displayed either.
I don't have android studio on this machine now to try, but I guess you can do something like
place_autocomplete_clear_button.onClick { place_autocomplete_search_input.text = "" }
where place_autocomplete_clear_button can be static import and onClick might be from anko
Figured it out. I had the method calls set up all wrong.
Here's how it should look like:
private fun clearButton() {
placeAutocompleteFragment?.view?.findViewById<View>(R.id.place_autocomplete_clear_button)?.setOnClickListener {
Log.d(TAG, "Cleared Button Clicked")
it.visibility = View.GONE
//do something
}
}

getContext() doesn't return null when fragment not attached?

An async task in a Fragment used to crash my app if the app was suddenly closed. The post execute code ran (e.g. show a Toast), but there was no app anymore.
I thought I fixed this by checking getContext() != null before running post execute code, but I got another crash.
java.lang.IllegalStateException:
at android.support.v4.app.Fragment.requireContext (Fragment.java:614)
at android.support.v4.app.Fragment.getResources (Fragment.java:678)
at android.support.v4.app.Fragment.getString (Fragment.java:700)
at com.grammarbud.android.grammarbud.MainFragment$2.onBillingServiceDisconnected (MainFragment.java:310)
I read that
Fragments now have requireContext(), requireActivity(), requireHost(), and requireFragmentManager() methods, which return a NonNull object of the equivalent get methods or throw an IllegalStateException.
https://developer.android.com/topic/libraries/support-library/revisions.html#27-1-0
Does this mean getContext() doesn't return null anymore? But then what does the below excerpt mean? Will it return null or not when not attached to the Activity? I don't understand the language.
The getActivity and getContext methods return nullable types because when the Fragment is not attached to an Activity, these methods already returned null. There's no change in behaviour, it's just explicitly marked now, so you can safely handle it.
https://stackoverflow.com/a/47253335/3268303
My code, which is run in a Fragment
private void connectToPlayStore() {
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponseCode) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
if (getContext() != null) {
queryProductDetails();
}
}
}
#Override
public void onBillingServiceDisconnected() {
// Line below ***CRASHES*** app if closed prematurely
SharedHelper.showToast(getContext(), getString(R.string.no_connection_to_play_store));
}
}
});
}
The SharedHelper function to show the Toast, which would catch if context is null, but execution doesn't get this far it seems to me
public static void showToast(Context context, String message) {
if (context != null) {
Toast newToast = Toast.makeText(context, message, Toast.LENGTH_LONG);
showToast(newToast);
}
}
So how to properly provide for the scenario when an async task is running in a fragment and the app is suddenly closed? Should I try to end connection with BillingClient.endConnection()? Will this guarantee that post execute code is not run? It doesn't explicitly say in the docs.
Also someone mentioned isAdded(). Should I check isAdded() instead of getActivity() != null && getContext() != null?

Sinch client wont start?

I am trying to use sinch an i am having erros i dont know why an i seem to have set it well.
and it is pointing me to this part of the code which
public void that(final String name) {
call.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
if ((getSinchServiceInterface() == null) || (!getSinchServiceInterface().isStarted())) {
getSinchServiceInterface().startClient(name);
}
}
});
}
the "getSinchServiceInterface().startClient(name);"
this is my error
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.obi.thinker.logins.call.SinchService$SinchServiceInterface.startClient(java.lang.String)' on a null object reference
at com.obi.thinker.logins.tabs.Chatting$caller$1.onClick(Chatting.java:386)
at android.view.View.performClick(View.java:5265)
at android.view.View$PerformClick.run(View.java:21534)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5683)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
I dont understand
if ((getSinchServiceInterface() != null && !getSinchServiceInterface().isStarted())) {
getSinchServiceInterface().startClient(name);
}
the callstack says:
getSinchServiceInterface() is null
And would suggest to read this:
https://stackoverflow.com/a/41477703/1979882
I was facing this issue for weeks. I literally rewrote my SinchService from scratch, removed Sinch altogether and tried starting fresh but all to no avail. It turns out I was simply initialising my call too early.
I recommend you move the code block:
if ((getSinchServiceInterface() != null || !getSinchServiceInterface().isStarted())) {
getSinchServiceInterface().startClient(name);
}
I moved mine to the very end of my onResume method and it seems to all work fine now. The reason this doesn't continually keep starting the client is because we are adding the 'if' statement to make sure it isn't already initialised.
If you find that the issue is still thrown, which was the case for me at one point, try calling it at the very end of the launch of your activity. It simply comes down to being called before your BaseActivity has a chance to initialise mSinchServiceInterface

Categories

Resources