I am building an application in which I have a Google Maps Fragment. In my MainActivity onCreate() method, I display this Google Maps Fragment which works fine in all cases except with airplane mode. If I turn airplane mode off while the application is running, my app crushes. I tried to include some Logs to see which methods are called and apparently, the following methods are called in order:
onPause() in MapFragment
onPause() in MainActivity
onCreate() in MainActivity
onResume() in MainActivity
and finally the app crushes in onMapReady(GoogleMap googleMap) in the MapFragment when I call
MapsInitializer.initialize(getActivity());
What I do not understand is how and why these methods are called when airplane mode is turned off and how to prevent the application from crushing.
Here is the stack trace of the error
2020-12-03 05:32:44.070 700-700/com.example.mutapamaps E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.mutapamaps, PID: 700
java.lang.NullPointerException: Context is null
at com.google.android.gms.common.internal.Preconditions.checkNotNull(com.google.android.gms:play-services-basement##17.3.0:11)
at com.google.android.gms.maps.MapsInitializer.initialize(Unknown Source)
at com.example.mutapamaps.Fragments.MapFragment.onMapReady(MapFragment.java:218)
at com.google.android.gms.maps.zzac.zza(Unknown Source)
at com.google.android.gms.maps.internal.zzaq.dispatchTransaction(Unknown Source)
at com.google.android.gms.internal.maps.zzb.onTransact(Unknown Source)
at android.os.Binder.transact(Binder.java:504)
at dt.aZ(:com.google.android.gms.dynamite_mapsdynamite#204516046#20.45.16 (040306-0):2)
at com.google.maps.api.android.lib6.impl.bi.run(:com.google.android.gms.dynamite_mapsdynamite#204516046#20.45.16 (040306-0):1)
at android.os.Handler.handleCallback(Handler.java:836)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6251)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
Here is my MainActivity
public class MainActivity extends AppCompatActivity{
public static FragmentManager fragmentManager;
public static final String LOG_TAG = "LOG_TAG";
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(LOG_TAG, "Main activity created");
super.onCreate(savedInstanceState);
// init airplane mode receiver
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
if(isAirplaneModeOn){
Log.i(LOG_TAG, "Airplane mode turned on");// handle Airplane Mode on
} else {
Log.i(LOG_TAG, "Airplane mode turned off");
}
}
};
this.registerReceiver(receiver, intentFilter);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
displayFragment(new MapFragment());
}
#Override
protected void onPause() {
super.onPause();
Log.i(LOG_TAG, "Main activity paused");
}
#Override
protected void onResume() {
super.onResume();
Log.i(LOG_TAG, "Main activity resumed");
}
public void displayFragment(Fragment fragmentActivity){
// start the transaction
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentActivity).
addToBackStack(fragmentActivity.getClass().getSimpleName()).commit();
}
}
Here is my MapFragment
public class MapsFragment extends Fragment implements
OnMapReadyCallback {
public MapsFragment() {
// Required empty public constructor
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_map, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
FrameLayout map_frame = view.findViewById(R.id.map_frame);
// configure map
MapView mapFragment = view.findViewById(R.id.map_view);
mapFragment.onCreate(savedInstanceState);
mapFragment.onResume();
mapFragment.getMapAsync(this);
View mapView = mapFragment.getRootView();
super.onViewCreated(view, savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
Log.i(LOG_TAG, "MapFragment resumed");
}
#Override
public void onPause() {
super.onPause();
Log.i(LOG_TAG, "Map fragment paused");
}
#Override
public void onMapReady(GoogleMap googleMap) {
Log.i(LOG_TAG, "onMapReadyCalled");
MapsInitializer.initialize(getActivity());
}
}
May you please help me understand what is going on and debug this piece of code.
Thank you in advance.
Change the onViewCreated with this part of code. Hope it helps.
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
FrameLayout map_frame = view.findViewById(R.id.map_frame);
// configure map
MapView mapFragment = view.findViewById(R.id.map_view);
mapFragment.onCreate(savedInstanceState);
mapFragment.onResume();
mapFragment.getMapAsync(this);
View mapView = mapFragment.getRootView();
super.onViewCreated(mapView , savedInstanceState);
}
Replace Your MapsFragment with this.
public class MapsFragment extends Fragment implements
OnMapReadyCallback {
Context context;
public MapsFragment() {
// Required empty public constructor
super();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_map, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
FrameLayout map_frame = view.findViewById(R.id.map_frame);
// configure map
MapView mapFragment = view.findViewById(R.id.map_view);
mapFragment.onCreate(savedInstanceState);
mapFragment.onResume();
mapFragment.getMapAsync(this);
View mapView = mapFragment.getRootView();
super.onViewCreated(view, savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
Log.i(LOG_TAG, "MapFragment resumed");
}
#Override
public void onPause() {
super.onPause();
Log.i(LOG_TAG, "Map fragment paused");
}
#Override
public void onMapReady(GoogleMap googleMap) {
Log.i(LOG_TAG, "onMapReadyCalled");
MapsInitializer.initialize(context);
}
}
Related
**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();
}
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()
}
});
I'm writing an Android App.
In my App I'm calling a map fragment right after an ACTION_IMAGE_CAPTURE intent is dispatched in the MainActivity.
The map fragment snippet is:
public class MapFragment extends Fragment implements OnMapReadyCallback {
.
.
.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
mView = inflater.inflate(R.layout.fragment_map, container, false);
return mView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mMapView = mView.findViewById(R.id.map);
if (mMapView != null) {
mMapView.onCreate(null);
mMapView.onResume();
mMapView.getMapAsync(this);
}
}
.
.
.
#Override
public void onMapReady(GoogleMap googleMap) {
MapsInitializer.initialize(getActivity());
mMap = googleMap;
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
}
If I don't dispatch the ACTION_IMAGE_CAPTURE intent the fragment works as planned, however, when dispatching the intent the context in MapsInitializer.initialize(getActivity()) is null
I think I need to better sync the the ACTION_IMAGE_CAPTURE intent completion and the onMapReady method but not sure how.
Any help will be appreciated.
Thanks,
Change to this,
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mMapView = mView.findViewById(R.id.map);
//initialize before calling map onCreate
MapsInitializer.initialize(getActivity());
if (mMapView != null) {
//instead of passing null its a good practice to use savedInstanceState
mMapView.onCreate(savedInstanceState);
mMapView.onResume();
mMapView.getMapAsync(this);
}
}
There are lot of (duplicate) questions and answers are available, I went through almost all of them and failed. On reference of this question, I made some changes recently.
Brief : In my app, MainActivity holds Fragment View Pager and FrangmentA,B and C. FrangmentA Shows a DialogFragment CDialog onClik. After dismissing CDialog I need to Call FragmentA's doReload() which is not happening here.
MainActivity
protected void onCreate(Bundle savedInstanceState){
...
mSectionsPageAdapter = new FragmentAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.container);
setupViewPager(mViewPager);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
int defaultValue = 0;
int page = getIntent().getIntExtra("One", defaultValue);
mViewPager.setCurrentItem(page);
}
private void setupViewPager(ViewPager viewPager)
{
FragmentAdapter adapter = new
FragmentAdapter(getSupportFragmentManager());
adapter.addFragment(new FragmentA(), "FragA");
adapter.addFragment(new FragmentB(), "FragB");
adapter.addFragment(new FragmentC(), "FragC");
viewPager.setAdapter(adapter);
}
FragmentA
public class FragmentA extends Fragment implements CDialog.Dismissed{
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
FragmentManager fm = getActivity().getFragmentManager();
DialogFragment f = new CDialog();
f.show(fm, "CDialog");
}
});
#Override
public void dialogDismissed() {
Log.e(DFD_1, "dialogDismiss Called" );// <-- This is not working*
doReload();
}
}
And CDialogue
public class CDialog extends DialogFragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
....
return v;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
...
dfd_1.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
getDialog().dismiss(); //<--when this happens*
}
});
}
#Override
public void onDismiss(DialogInterface dialog) {
if (getActivity() != null && getActivity() instanceof Dismissed) {
((Dismissed) getActivity()).dialogDismissed();
}
super.onDismiss(dialog);
}
public interface Dismissed {
public void dialogDismissed(); //<-- FragmentA implements this
}
}
You can always have direct callback to your Fragment itself.
First step, is to set targetFragment using setTargetFragment():
DialogFragment#setTargetFragment(Fragment fragment, int requestCode);
I do it this way:
public void showDialogFragment(Fragment targetFragment, AppCompatDialogFragment appCompatDialogFragment, int requestCode) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
appCompatDialogFragment.setTargetFragment(targetFragment, requestCode);
fragmentTransaction.add(appCompatDialogFragment, appCompatDialogFragment.getClass().getSimpleName());
fragmentTransaction.commitAllowingStateLoss();
}
and then call to this method to open dialog fragment as:
public static final int RC_CDIALOG = 111;
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
showDialogFragment(FragmentA.this, new CDialog(), RC_CDIALOG);
}
});
Then, in your DialogFragment's onDismissListener(), have some code like below:
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (getTargetFragment() instanceof FragmentA)
((FragmentA) getTargetFragment()).doReload();
}
What you did this way is:
Show Dialog Fragment "CDialog" along with telling it that your target fragment is "FragmentA" whose reference you can use incase you have something to do with it. In your case you had to call doReload();
I have made a MapFragment which basically is GoogleMaps. This Fragment is called in an activity, through the fragmentManager and fragment transaction. I would like to tweak my Map now to add some markers and dynamic zoom aswell. I am a bit struggling with this at the moment. Maybe some of you can take a look and help out.
MapsActivity.java
public class MapsActivity extends Activity {
Context context;
Button showMap;
Button showMyLocation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
context = this;
showMap = (Button)findViewById(R.id.showMap);
showMap.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MapFragment mf = new MapFragment();
MapFragment.googleMap.addMarker(new MarkerOptions().position(new LatLng(Double.parseDouble("2.900000"), Double.parseDouble("1.55555")))
.title("oh yeah"));
ft.add(R.id.maps_layout, mf);
ft.commit();
}
});
showMyLocation = (Button)findViewById(R.id.showMyLocation);
showMyLocation.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
MapFragment.java
public class MapFragment extends Fragment {
public static GoogleMap googleMap;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.map_fragment_layout, container, false);
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
googleMap = ((com.google.android.gms.maps.MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap();
}
}
After updating to the code above it gives me this error in the stacktrace :
java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.maps.model.Marker com.google.android.gms.maps.GoogleMap.addMarker(com.google.android.gms.maps.model.MarkerOptions)' on a null object reference
at com.example.dell.exampleapplication.MapsActivity$1.onClick(MapsActivity.java:42)
at android.view.View.performClick(View.java:5156)
at android.view.View$PerformClick.run(View.java:20755)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5834)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)
This may provide extra information for help.
Thanks for you help.
ok here is how i use it:
public class Mapfrag2 extends Fragment{
public static GoogleMap map;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.map_fragment_layout, container, false);}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
map = ((MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap();
if (savedInstanceState == null) {
map.setMyLocationEnabled(true);
CameraUpdate center = CameraUpdateFactory.newLatLng(new LatLng(30.76793169992044, 1.98180484771729));//moving the map to defined position
CameraUpdate zoom = CameraUpdateFactory.zoomTo(5);
map.moveCamera(center);
markerPoints = new ArrayList<LatLng>();
map.animateCamera(zoom);
map.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() {
#Override
public void onInfoWindowClick(Marker marker) {
//doing something when pressing marker's bubble
}
});
}
}
}
in your activity just call
MapFrag2.map
example
adding markers from activity
Mapfrag2.map.addMarker(new MarkerOptions().position(
new LatLng(Double.parseDouble(lat), Double.parseDouble(lon)))
.title(title).snippet(snippet));
clearing the map
Mapfrag2.map.clear();
Edit 2
try to change this part of code
public void onClick(View v) {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MapFragment mf = new MapFragment();
MapFragment.googleMap.addMarker(new MarkerOptions().position(new LatLng(Double.parseDouble("2.900000"), Double.parseDouble("1.55555")))
.title("oh yeah"));//move this to the end
ft.add(R.id.maps_layout, mf);
ft.commit();
//here you add your marker
}
I found a solution very easy. I just followed the steps in this wiki page (http://www.techotopia.com/index.php/Working_with_the_Google_Maps_Android_API_in_Android_Studio). It basically explains everything to you.
Simple tip: make your MapActivity extend FragmentActivity !