I have a dialog with a button. I want to change a TextView (txtTotalDist) in the parent fragment when the button is clicked and the dialog is closed.
I tried several methods without success:
rootView.btnDialogOK.setOnClickListener {
val frag: ParentFragment()
frag.txtTotalDist.text="A"
//Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
(activity as ParentFragment).txtTotalDist.post { txtTotalDist.text = "A" }
//cannot be cast to ParentFragment
txtTotalDist.post { txtTotalDist.text = "A" }
//Attempt to invoke virtual method 'boolean android.widget.TextView.post(java.lang.Runnable)' on a null object reference
txtTotalDist.text = "A"
//Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
dismiss()
}
I also tried to find a function in the parent fragment to override, e.g. onAttach, onResume, onStart, onViewStateRestored, but none of them start when I close the dialog. How can I do it properly?
EDIT / SOLUTION
build.gradle Module:
implementation 'androidx.fragment:fragment-ktx:1.4.0'
Host fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("action")
if (result=="del") refreshStatusBar()
}
}
Dialog fragment:
rootView.btnDialogRemove.setOnClickListener {
setFragmentResult("requestKey", bundleOf("action" to "del"))
dismiss()
}
If the second fragment is nested inside in first fragment, you can pass a callback to second fragment and invoke it after closing dialog, or you can return an observable from second fragment to observe by first one.
But if your designed fragments are sibling with each other, it depends on your designed code base. If you are using fragment manager directly, you can do the same approach as above and add second fragment instead of replacing it. But if you have to replace or you are using navigation component, there is a better way to handle it like we do between two activities.
You can add below code lines to first fragment:
setResultListener("yourKey") { key, Any ->
val result = anything you need to return
//Todo something about result
}
And in second activity when you want to set data after closing dialog or whenever:
val result = any result
setResult("yourKey",result)
Also you can check the below url for more information about it.
https://developer.android.com/training/basics/fragments/pass-data-between#kotlin
Related
this is what I want to achieve:
I'm in Activity A with couple options to choose from (buttons)
after clicking any of them, I want to be taken to Activity B
Activity B should contain a constant part (audio) and a Fragment with image and text, depending on the button you choose in Activity A.
Is it achievable? I tried to both startActivity and getSupportFragmentManager (etc.) as my onClick method but with no use, maybe there's another way?
In activity B create a method (static in java, companion in kotlin) to create an intent:
companion object {
const val ARG_FRAGMENT_TO_LOAD = "argFragment"
fun newIntent(context:Context, fragmentTagToLoad:String): Intent {
return Intent(context, ActivityB::class.java).apply {
putString(ARG_FRAGMENT_TO_LOAD, fragmentTagToLoad)
})
}
}
Then, in activity A you can use this intent to start activity B.
myButton?.setOnClickListener {
startActivity(ActivityB.newIntent(this, SomeFragmentToLoad.ARG_TAG))
}
Then, again in activity B you will receive this argument in the intent, so that you can handle it. It is typically done inside onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val fragmentTagToLoad = intent.getStringExtra(ARG_FRAGMENT_TO_LOAD) ?: ""
if(fragmentToLoad.isNotEmpty() {
// Use fragment manager to load fragment into container.
}
...
}
Finally, in activity B you can use the argument to load the desired fragment using a (usually support) fragment manager.
Of course you should define ARG_TAG as a String constant inside SomeFragmentToLoad.
Is there any better way to send back the data to the previous fragment/parent fragment other than listener?
I have a fragment which consists of list of items. Clicking on the items will open a bottom sheet fragment. While closing the bottom sheet popup I need to pass data back to the fragment itself.
What I have done so far is created a listener and implemented it.
It really depends on what components you're using. If you are using Android Jetpack components then check out this article: LINK
You should be able to pass data back and forth similar to passing data with startActivityForResult()
Also, while you're at it please check out the official documentation too, there's a good example that will help you understand this better: LINK
Although you mention any way other than listener, but according to
documents:
Starting with Fragment 1.3.0-alpha04, each FragmentManager
implements FragmentResultOwner. This means that a FragmentManager can
act as a central store for fragment results. This change allows
components to communicate with each other by setting fragment results
and listening for those results...
Sets the FragmentResultListener for a given requestKey. Once the given
LifecycleOwner is at least in the STARTED state, any results set by
setFragmentResult using the same requestKey will be delivered to the
callback. The callback will remain active until the LifecycleOwner
reaches the DESTROYED state or clearFragmentResultListener is called
with the same requestKey.
To pass data back to fragment A from fragment B, first set a result listener on fragment A, the fragment that receives the result. Call setFragmentResultListener() on fragment A's FragmentManager, as shown in below:
in your BottomSheet Class:
btncloseBottomSheet.setOnClickListener {
val result = Bundle().apply {
// put your data in bundle
putInt("MY_KEY", 6)
}
setFragmentResult("requestCode", result)
dismiss()
}
in your previous fragment/parent fragment, you need to implement FragmentResultListener:
class PreviousFragment : FragmentResultListener {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set fragment listener
parentFragmentManager.setFragmentResultListener(
"requestCode",
viewLifecycleOwner,
this
)
}
...
// get result from other fragments by FragmentResultListener
override fun onFragmentResult(requestKey: String, result: Bundle) {
when (requestKey) {
"requestCode" -> {
val resultFromBundle = result.getInt("MY_KEY")
// Do somthing
}
}
}
}
I have an activity and a fragment within that activity. The fragment is loaded within the activity onCreate().
if (!supportFragmentManager.isDestroyed) {
val fragmentTransaction = this.supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.containerLayout, fragment).commit()
}
Inside the fragment, I am performing an API call and when the result is received, the activity gets the callback and the result is passed to the fragment from the activity.
The issue is when I load this activity and when the API is still on call if I press the device recents button then the app crashes showing the below exception.
Caused by java.lang.IllegalStateException Can not perform this action after onSaveInstanceState
I understand that the problem is the fragment tries to commit after onSaveInstanceState is called. But how is that happening I am not clear. I went through the article too. It says three points as solution.
To commit the fragment within onCreate() which I am already doing.
Not to commit in onPostExecute() which is not applicable to me.
Use commitAllowingStateLoss() only as a last resort.
Should I need to change commit() to commitAllowingStateLoss()? As I went through the docs, I don't feel that safe too. Could someone suggest to me the right way?
I didn't use commitAllowingStateLoss(). I put the code as:
var isAnException: Boolean = false
try {
if (!supportFragmentManager.isDestroyed) {
val fragmentTransaction = this.supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.containerLayout, fragment).commit()
}
} catch (exception: IllegalStateException) {
isAnException = true
}
and in onResume() of my activity, I added the below code to make the fragment work when taken from the recents.
override fun onResume() {
if (isAnException) {
isAnException = false
//fragment load and set the views
}
super.onResume()
}
I have Bottom Navigation with fragment and Retrofit for api call
I call loadFragment for switching the fragments as below
private boolean loadFragment(Fragment fragment) {
//switching fragment
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
return true;
}
return false;
}
The problem is my app crashes if i start clicking randomly and change fragment quickly, when i check logcat it shows NPE while setting some data.
My Fragment consist of following methods
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_current, container, false);
unbinder = ButterKnife.bind(this, view);
context = getActivity();
callCategoryAPI();
return view;
}
callCategory() is api retrofit call in separate controller class and brings back response via interface and then set data.
So what i suspect is my API returns response (as its asynchronous) but views are not available as user has changes the fragment (quickly) so views are null.
I already tried setuserVisibleHint also tried block click for 1200ms and also checked is my fragment view created, How to stop this crash? and make retrofit call Lifecycle dependent?
Logcat
at com.example.CurrentFragment$1.onApiSuccess(CurrentFragment.java:82)
at com.example.services.current_statement.CurrentStatementController$2.onResponse(CurrentStatementController.java:91)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:171)
at android.app.ActivityThread.main(ActivityThread.java:6606)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:518)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
--
Fatal Exception: java.lang.NullPointerException: Attempt to invoke
virtual method 'void
android.widget.TextView.setText(java.lang.CharSequence)' on a null
object reference
at com.example.fragment.statements.CurrentFragment$1.onApiSuccess(CurrentFragment.java:82)
at com.example.services.current_statement.CurrentStatementController$2.onResponse(CurrentStatementController.java:91)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:171)
at android.app.ActivityThread.main(ActivityThread.java:6606)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:518)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
set in your fragment
if(activity != null && isAdded) {
// perform your task
}
Check in fragment
if(activity != null && isAdded){
Perform your operation
}
set in your fragment
if(getActivity() != null && isAdded) {
//do your operation
}
This is the problem:
Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
You would like to set text to a textview which is null. You should check at first that your textview is not null like this:
if (textview != null){
textview.setText("mytext");
}
Use this library below to block the activity until the network operation is finished.
Spot Dialog
This is what I used for my app in the image which I showed you earliar.
I may be late, but i hope i can help other developer as well , for i have encountered this problem too. just use isAdded() in your condition at onResponse callback inside the if-else
ex: inside your onResponse callback :
if (isAdded()){
// success response
} else {
if(isAdded()){
// error response
}
}
code is for kotlin in android:
TextView?.text
here ? mean not null
use it like shows as above
If you change navigation drawer menu item quickly, current fragment will replace by new one too. Android can remove any ongoing calculation that is running in main thread. But if any heavy task like background network response result or handler delayed task that has a reference of fragment can find null because fragment has already detach or replace by new one.
So you just have to check that, your desire fragment is still added in activity or not before process response data that has come lately.
if(frag.isAdded()){
//lets process your delayed response data
}
Hello so I am trying to pass an array list from my activity to the fragment and this is what I did :
FirstActivity :
AdminInterface instanceForInterface;
OnCreate
//
System.out.println(results.size) ; //works fine
instanceForInterface.onDataRecieved(results); // here I am getting the exception
//
public interface AdminInterface {
void onDataRecieved(ArrayList <Result> response);
}
public void setInterface(UserFragment anInterface) {
this.instanceForInterface = anInterface;
}
Fragment
OnActivityCreated
((FirstActivity) getActivity()).setInterface(this);
#Override
public void onDataRecieved(ArrayList<Result> response) {
processData(response);
}
Exception
Attempt to invoke interface method 'void **************.onDataRecieved(java.util.ArrayList)' on a null object reference
What I think :
I am calling this line
instanceForInterface.onDataRecieved(results); in OnCreate()
before the initialisation of
((FirstActivity) getActivity()).setInterface(this); in OnActivityCreated()
Solution Please ??
Thank You
The problem is that your fragment's onActivityCreated() method is invoked after your activity's onCreate() method.
The smallest change you can make to achieve the behavior you want is to use the onResumeFragments() method in your activity. That is, delete the line instanceForInterface.onDataRecieved(results); from your onCreate and add this code:
#Override
protected void onResumeFragments() {
super.onResumeFragments();
instanceForInterface.onDataRecieved(results);
}
onResumeFragments() will be invoked by the system after both your activity's onCreate() and your fragment's onActivityCreated() methods.
That being said, chances are quite good that you would be better off with a different approach entirely. For instance, you could have your activity expose a getter for results and have your fragment retrieve the results to work with (rather than have your activity store a reference to your fragment).
Further reading about the Activity and Fragment lifecycles:
https://developer.android.com/guide/components/activities/activity-lifecycle.html
https://developer.android.com/guide/components/fragments.html