Realm in Fragment causing NullPointerException - java

I'm confused as to how to handle Realm in fragments when the fragment's activity extends a base activity that controls Realm.
In other words, I have this structure:
Base Activity -> User Activity -> Base Fragment -> Fragment
Everything works fine, until I exit the activity (i.e. go back to the previous activity). I get this error:
java.lang.RuntimeException: Unable to destroy activity {UserActivity}:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
io.realm.Realm.close()' on a null object reference
...
Caused by: java.lang.NullPointerException: Attempt to invoke virtual
method 'void io.realm.Realm.close()' on a null object reference at
BaseFragment.onDestroyView
My base Activity:
public abstract class BaseActivity extends AppCompatActivity {
private Realm realm;
protected abstract int getLayoutResource();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
realm = Realm.getDefaultInstance();
}
#Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
public Realm getRealm() {
return realm;
}
}
My main Activity (where the Fragments are created):
public class UserActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String type = getIntent().getStringExtra("type");
Fragment fragment = null;
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
return;
}
if (type.equals("first")) {
fragment = new FirstFragment();
} else if (type.equals("second")) {
fragment = new SecondFragment();
}
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
}
}
#Override
protected int getLayoutResource() {
return R.layout.activity_user;
}
}
And all of my Fragments extend a base Fragment. Here is my base Fragment:
public abstract class BaseFragment extends Fragment {
private Realm realm;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
return inflater.inflate(getFragmentLayout(), container, false);
}
#Override
public void onDestroyView() {
super.onDestroyView();
realm.close();
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// some realm operation
}
});
}
protected abstract int getFragmentLayout();
}
What's causing the problem? Am I managing Realm improperly?

Get rid of the realm.close(); in your BaseFragment that is going to be closed when you destroy the activity, so you are going to be closing it twice, which is why you get the NPE. So if you remove the realm.close(); in your Fragment, and let the Activity handle closing Realm.

Related

ClassCastException occurs when calling another fragment's method

**Re-attached the relevant code
When I run the email app in FragmentB, the activity's 'onStop' is called.
At this time, FragmentA's method in 'onStop' does not work.
※ FragmentA and FragmentB have the same parent Activity
※ No error occurs when the app is terminated in FragmentB.
MainActivity.java
public class MainActivity extends FragmentActivity {
public Fragment fragmentA, fragmentB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
return;
}
fragmentA = new FragmentA();
fragmentB = new FragmentB();
fragmentA.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragmentA).commit();
}
}
#Override
protected void onStop() {
super.onStop();
((FragmentA) getSupportFragmentManager().findFragmentById(R.id.fragment_container)).saveData();
}
}
FragmentA.java
View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_timer, container, false);
if(isLoadData){ loadData(); }
return view;
}
public void saveData() {
SharedPreferences timerSharedPref = getActivity().getSharedPreferences("timer", Context.MODE_PRIVATE);
//...
SharedPreferences.Editor editor = timerSharedPref.edit();
String json = jsonArray.toString();
editor.putString("jsonString", json);
editor.apply();
}
FragmentB.java
public class FragmentB extends Fragment implements View.OnClickListener {
View view;
CardView cvContactUs;
MainActivity activity;
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
activity = (MainActivity) getActivity();
}
#Override
public void onDetach() {
super.onDetach();
activity = null;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_b, container, false);
cvContactUs = view.findViewById(R.id.cv_contact_us);
return view;
}
#Override
public void onStart() {
super.onStart();
cvContactUs.setOnClickListener(this);
}
#Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.cv_contact_us) {
sendEmail(getActivity(), "Ask a question", new String[]{"help_center#gmail.com"});
}
}
public static void sendEmail(Context context, String title, String[] receivers){
Intent email = new Intent(Intent.ACTION_SEND);
email.putExtra(Intent.EXTRA_SUBJECT, title);
email.putExtra(Intent.EXTRA_EMAIL, receivers);
email.setType("message/rfc822");
context.startActivity(email);
}
}
You say you are getting a ClassCastException. The only class cast from the code you posted is
(FragmentA) getSupportFragmentManager().findFragmentById(...)
So check whether findFragmentById might return FragmentB or something else. Maybe you want to
either cast to the common parent activity
check whether the returned value is an instance of FragmentA before casting
You have two Fragments in the stack and a single ID and the findFragmentByID() method returns the last fragment added to the container. very well when the application ends with the FragmentA I believe that you don't have any error because in the onStop() method you are casting with the FragmentA, but if you end with the FragmentB in this case you are casting the FragmentB by the FragmentA which throws the ClassCastException exception. so I suggest you use the findFragmentByTag() method here is an example
When you add fragments:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* rest of your code
*/
// when you add the Fragment A
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, new FragmentA(), "A")
.commit();
// when you add the Fragment A
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, new FragmentB(), "B")
.commit();
}
and when you call the onStop() methode:
#Override
protected void onStop() {
super.onStop();
// ((FragmentA) getSupportFragmentManager().findFragmentByTag("A")).save();
// ((FragmentB) getSupportFragmentManager().findFragmentByTag("B")).save();
Fragment f = getSupportFragmentManager().findFragmentByTag("A");
// you check if the fragment is add
if (f != null)
((FragmentA) f).save();
}

Views of Fragment Layout causing NullPointerException

I'm trying to use View Pager with Fragment State Adapter. But getting a NullPointerException when trying to invoke a Fragment method after creating its instance.
Here is the activity class and Adapter class:
public class AddScheduleActivity extends AppCompatActivity {
private FragmentOverview fragmentOverview;
private FragmentTodo fragmentTodo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_schedule);
ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));
fragmentOverview = new FragmentOverview();
fragmentOverview.setTargetView(); //ERROR IS HERE
My view pager adapter is:
private class ViewPagerAdapter extends FragmentStateAdapter {
public ViewPagerAdapter(#NonNull FragmentManager fragmentManager, #NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
#NonNull
#Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return fragmentOverview;
case 1:
return fragmentTodo;
default:
return null;
}
}
#Override
public int getItemCount() {
return 2;
}
}
My FragmentOverView class is:
public class FragmentOverview extends Fragment{
private FrameLayout mTargetFrameLayout;
private FrameLayout mDescriptionFrameLayout;
public FragmentOverview() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_overview, container, false);
mDescriptionFrameLayout = rootView.findViewById(R.id.descriptionView_frameLayout_OverviewFragment);
mTargetFrameLayout = rootView.findViewById(R.id.targetView_frameLayout_OverviewFragment);
return rootView;
}
public void setTargetView() {
mTargetFrameLayout.removeAllViews();
if (progress.maxProgress != null) {
//ADD A CHILD VIEW
mTargetFrameLayout.setVisibility(View.VISIBLE);
} else mTargetFrameLayout.setVisibility(View.GONE);
}
}
But when I call the setTargetView() method after creating instance of FragmentOverview, I'm getting a NullPointerException. From log I noticed that the mTargetFrameLayout is null.
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.FrameLayout.removeAllViews()' on a null object reference
I think this is because the activity lifecycle methods onCreate(), onStart(), onResume() are called before the fragment lifecycle methods onCreate() & onCreateView(). But how to overcome this problem? Or am I trying to do in a wrong way?
I think your assumption is right. The fragment layout is generated in onViewCreated() of FragmentOverview class. But this method start executing after the Activity lifecycle methods onCreate(), onStart() and onResume() are executed. You can checkout the lifecycle relation of Activity and Fragment by overwriting lifecycle methods and adding logs as mentioned by #Max_Hockeborn.
So you will always get the views i.e mTargetFrameLayout null if you call setTargetView() before executing onCreateView() in FragmentOverview.
To solve this, you can add a callback method that will be executed in parent class after executing the onCreateView() of FragmentOverveiw. And in that callback you can invoke setTargetView().
Here I'm providing a possible solution for your case:
In FragmentOverview add this lines:
public class FragmentOverview extends Fragment{
//all your fields;
private OverviewCallbacks callbacks;
public FragmentOverview(OverviewCallbacks callbacks) {
this.callbacks = callbacks;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//inflate the rootView and find all the childViews;
//I think your codes are okey;
}
//Overwrite this method;
//You can also overwrite onViewCreated() lifecycle method;
//And add call this callback method from here;
#Override
public void onResume() {
super.onResume();
callbacks.onReach();
}
public void setTargetView() {
//Do what you want to do;
}
public interface OverviewCallbacks {
void onReach();
}
}
Thats all for FragmentOverView class.
Now modify your Activity class as follows:
public class AddScheduleActivity extends AppCompatActivity {
private FragmentOverview fragmentOverview;
private FragmentTodo fragmentTodo;
#Override
protected void onCreate(Bundle savedInstanceState) {
//Your codes
fragmentOverview = new FragmentOverview(new FragmentOverview.OverviewCallbacks() {
#Override
public void onReach() {
setTargetView();
}
});
}
}
Hope this will work. Comment if anything gone wrong.
This is just a fast idea that I couldn't test yet.
You could try to change this:
public class AddScheduleActivity extends AppCompatActivity {
private FragmentOverview fragmentOverview;
private FragmentTodo fragmentTodo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_schedule);
ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));
fragmentOverview = new FragmentOverview();
fragmentOverview.setTargetView(); //ERROR IS HERE
to this:
public class AddScheduleActivity extends AppCompatActivity {
private FragmentOverview fragmentOverview = new FragmentOverview();
private FragmentTodo fragmentTodo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_schedule);
ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));
fragmentOverview.setTargetView(); //ERROR IS HERE
To check your assumption that your problem is based on the call order you can add some logs and check the order they are called in with Logcat (given you use android studio)
Something like this:
public class AddScheduleActivity extends AppCompatActivity {
private FragmentOverview fragmentOverview;
private FragmentTodo fragmentTodo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_schedule);
ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));
Log.d("YOUR_TAG","Activity onCreate 1");
fragmentOverview = new FragmentOverview();
fragmentOverview.setTargetView(); //ERROR IS HERE
and
public class FragmentOverview extends Fragment{
private FrameLayout mTargetFrameLayout;
private FrameLayout mDescriptionFrameLayout;
public FragmentOverview() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_overview, container, false);
mDescriptionFrameLayout = rootView.findViewById(R.id.descriptionView_frameLayout_OverviewFragment);
mTargetFrameLayout = rootView.findViewById(R.id.targetView_frameLayout_OverviewFragment);
Log.d("YOUR_TAG","FragmentOverview onCreateView 1");
return rootView;
}
public void setTargetView() {
Log.d("YOUR_TAG","FragmentOverview setTargetView 1");
mTargetFrameLayout.removeAllViews();
if (progress.maxProgress != null) {
//ADD A CHILD VIEW
mTargetFrameLayout.setVisibility(View.VISIBLE);
} else mTargetFrameLayout.setVisibility(View.GONE);
}
}
The last one could be interesting for you to see if onCreateView (and the initiation of the variables that cause your problems) is finished before the setTargetView() function is called.

Calling a function when an activity is returned to from fragment

I have a fragment that I call like so from my activities onCreate
android.app.FragmentManager fragmentManager = getFragmentManager();
DeviceControlFragment newFragment = new DeviceControlFragment();
newFragment.device = device;
fragmentManager.beginTransaction()
.replace(R.id.content_frame, newFragment)
.commit();
In the fragment I can either pop the back stack or hit back like so:
getFragmentManager().popBackStack();
OR
getActivity().onBackPressed();
But I need something in the activity so that I know we have returned there, is there something similar to onActivityResult that I can override in the activity to know when the fragment has been 'finished'
Declare a interface inside fragment and implement it in your activity and send response to activity inside onDetach(),onDestroy or onPaused method of fragment, any of them which suits you. I will prefer onDetach or onDestroy
Here is sample for that:
public class MyFragment extends Fragment {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
private Test mListener;
public interface Test{
void imLeavingYou();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mListener = (Test) context;
}
#Override
public void onDetach() {
super.onDetach();
if(mListener != null){
mListener.imLeavingYou();
}
}
}
And in your Activity do this:
public class MyActivity extends AppCompatActivity implements MyFragment.Test {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
}
#Override
public void imLeavingYou() {
// Okay thanks for informing
}
}
You should probably set a onBackStackChanged() listener on your fragment manager.
Example:
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
//here you can check stuff. like:
final Fragment mCurrentFragment = getFragmentManager().findFragmentById(R.id.container);
//or get the number of fragments currently on your stack:
getFragmentManager().getBackStackEntryCount()
}
});

OnClick not triggered inside Fragment

Got two empty Activites(A and B), which just hold two fragments inside ViewPager of Activity A.
I do not have code errors, everything seems fine.
When I lunch my app and click on button, nothing happens.I was trying just to log something, but still nothing is happening.
I am using ButterKnife, and so far everything was perfect.I got almost same Fragment and it is performing fine, but OnClick inside Fragment B is not working.I tried to add some more OnClick methods, but none of them worked for me.XML looks good,looks almost same as fragment A.
Fragment B is not complex, it just three TextViews and Button.
Here is code for my fragment B:
public class ForgotPasswordFragmentComplete extends BaseFragment
implements BaseView {
private Realm realm;
private Email model;
#Bind(R.id.btn_resend)
AppCompatButton resendEmail;
#Inject
ForgotPasswordPresenter presenter;
#OnClick(R.id.btn_resend)
public void resendButton() {
Log.d("ResendOnclick", "Checking OnClickMethod ");
Realm realm2 = getRealm();
RealmQuery<Email> queryUserResend = realm2.where(Email.class);
Email resultResend = queryUserResend.findFirst();
ForgotPasswordPayload forgotPayload = new ForgotPasswordPayload(resultResend.getUsername());
this.presenter.subscribe(forgotPayload);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerForgotPasswordCompletedComponent.builder()
.applicationComponent(
((AndroidApplication) getActivity().getApplication()).getApplicationComponent())
.forgotPasswordCompletedModule(new ForgotPasswordCompletedModule())
.build()
.inject(this);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
#Override
protected void onCreateViewWidgetInitialization(View view) {
super.onCreateViewWidgetInitialization(view);
}
#Override
public void onStart() {
super.onStart();
getEmail();
}
public void getEmail(){
Realm realm = getRealm();
RealmQuery<Email> queryUser = realm.where(Email.class);
Email result1 = queryUser.findFirst();
resendEmailTxt = (AutoResizeTextView) getView().findViewById(R.id.resend_user_email);
if (resendEmailTxt != null) {
this.resendEmailTxt.setText(result1.getUsername());
}
}
#Override
public void onDestroy() {
super.onDestroy();
realm.close();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_forgot_password_fragment_complete, container, false);
}
#Override
protected int getFragmentLayoutId() {
return R.layout.fragment_forgot_password_fragment_complete;
}
You need to bind the fragment's view object before you can use it. Try putting the following code inside onCreateView() :
View rootView = inflater.inflate(R.layout.fragment_forgot_password_fragment_complete, container, false);
ButterKnife.bind(this, rootView);
return rootView;

Call a fragmentMethod from another fragment, but can't refer to them in parentFragmentActivity's View pager

I m new to android and i m having trouble with fragment communication, reading about it discovered the standard method of creating an interface and implementing it from the parentActivity which doesn't work in my case which is as follows.
I have an Activity named as ActivityA which extends fragment activity,in which i have a view pager for i want to keep two fragments namely fragmentA and fragmentB which should be swipable fragments.
fragmentA has a button which onClicked should make a change in fragmentB, to be precise on click it should add a string in the listView in fragmentA.
Now i have created the both the fragments's layouts as well as added the viewPAger to my FragmentActivity, they work great on swiping, the function is executed onButtonClick of fragmentA and logic works perfectly, after this is i want the changes to be reflected in fragmentB.
Using the interface method i can't find the reference to my fragment by id as it is added by viewPager to ActivityA
How do i do this??
help me out.
Stackoverflow says don't ask questions with out code so
Here is the Sample code
Activity A which has a virewPAger, need help how to get my fragment's Reference from the viewPager
public class ActivityA extends FragmentActivity implements FragmentA.Communicator
{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_A);
viewPager= (ViewPager) findViewById(R.id.pager);
fragmentManager=getSupportFragmentManager();
viewPager.setAdapter(new myAdapter(fragmentManager));
}
class myAdapter extends FragmentPagerAdapter{
public myAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = null;
switch (i){
case 0: fragment = new listOfIDsScanned();
break;
case 1: fragment = new displayCounter();
break;
default: //something witch won't crush the app
break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
}
This is fragmentA , actual fragment name is something else just for simple representation calling it fragmentA, which has an interface Communicator to communicate between fragments
public class fragmentA extends android.support.v4.app.Fragment implements View.OnClickListener
{
TextView counter;
Button scanStudentID;
int count=0;
String name=null,rollNo=null;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.display_counter,container,false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
scanStudentID= (Button) getActivity().findViewById(R.id.startScanning);
scanStudentID.setOnClickListener(this);
}
#Override
public void onClick(View v)
{
switch(v.getId()){
case R.id.startScanning:
startScanning();
break;
}
}
public void startScanning()
{
startActivityForResult(new Intent(getActivity(),Barcode.class),1);
}
public interface Communicator
{
// necessary methods
}
}
now finally fragmentB, in which methodToBeCalled(parameters) has to be invoked on the button click in fragementA
public class fragementB extends android.support.v4.app.Fragment
{
ListView listView;
ArrayList<String> rollNo= new ArrayList<>();
ArrayList<String> studentName= new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_list_of_ids_scanned,container,false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView= (ListView) getActivity().findViewById(R.id.listView);
}
public void methodToBeCalled(String rollNo,String studentName)
{
if(!alreadyScanned(rollNo))
{
}
else
{
}
}
boolean alreadyScanned(String rollNo)
{
return true;
}
}

Categories

Resources