I'm creating a simple application using Mapbox (using mapbox-android-sdk:7.1.0 ) in a fragment.
I have an activity which is composed of a FrameLayout (containing the Fragments) and a button.
At the beginning, the Fragment1 (containing the map) is displayed in the FrameLayout. When the user clicks on the button, the Fragment1 is replaced by the Fragment2 (containing a TextView).
During the transition there is a short black screen.
When I use Mapbox in an activity I don't have any problem, It seems to happen when the method onDestroyView() is called.
MainActivity.java :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Load Fragment1 containing the map
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment1()).commit();
AppCompatButton button = findViewById(R.id.btn_changeFragment);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Load Fragment2 containing a TextView
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment2()).addToBackStack(null).commit();
}
});
}
}
Fragment1.java :
public class Fragment1 extends SupportMapFragment {
private MapView mapView;
public Fragment1(){}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(getContext(), getString(R.string.mapbox_token));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_1, container, false);
mapView = view.findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(#NonNull final MapboxMap mapboxMap) {
mapboxMap.setStyle(Style.LIGHT, new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style) {
// Configure the map
}
});
}
});
return view;
}
#Override
public void onStart() {
super.onStart();
mapView.onStart();
}
#Override
public void onResume() {
super.onResume();
mapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mapView.onPause();
}
#Override
public void onStop() {
super.onStop();
mapView.onStop();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
#Override
public void onDestroyView() {
super.onDestroyView();
mapView.onDestroy();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
}
Using this Attribute app:mapbox_renderTextureMode="true" while loading mapview inside the fragment did the trick for me.
This is a known issue with how MapFragment's behave: https://github.com/mapbox/mapbox-gl-native/issues/9570. Per the ticket, there are currently two possible ways to resolve this issue in your app.
Either:
Use a bitmap of the map in an ImageView while transitioning instead of a MapView. You can then make the MapView visible as part of the OnMapReady callback in your activity.
Or:
Use a TextureView implementation instead. This can be enabled via MapboxMapOptions or .xml attributes. You should also keep in mind that this solution may lead to performance issues down the road.
I tried the first solution proposed by #riastrad but I didnĀ“t find how to make a bitmap of the mapView ( I always had a white screen with the mapbox logo at the bottom).
Anyway, I tried to use setAlpha to change the opacity of the mapView, and it works. I don't realy know why it doesn't work with setVisibility(View.INVISIBLE).
So before to load the Fragment2, I call a method from Fragment1 to set the opacity of the mapView to 0.
MainActivity.java
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if(currentFragment instanceof Fragment1){
// Set the opacity of the mapView to 0
((Fragment1) currentFragment).transition();
// Load Fragment2 containing a TextView
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment2()).addToBackStack(null).commit();
}else {
getSupportFragmentManager().popBackStack();
}
}
});
Fragment1.java
public void transition(){
mapView.setAlpha(0f);
}
Related
I have a fragment (ApartmentFragment) which is a page that show details of an apartment. I want to add a little map to show where the apartment is.
just for clarity, I'm using the MaterialNavigationDrawer that is on github, my app is composed by a single Activity (the navigation drawer) and many fragments.
Every stackoverflow post I have read didn't helped me. Many of them were a bit confusing. I don't want only the map inside ApartmentFragment, i want to show other things plus the map.
Which is the best way to do this?
Is possible to put a mapfragment inside a fragment? or I need to transform ApartmentFragment in activity?
most of what u read it's correct. You'll just use MapView instead of MapFragment
https://developer.android.com/reference/com/google/android/gms/maps/MapView.html
then on your ApartmenFragment you have to make all callbacks to mapview
onCreate(Bundle)
onResume()
onPause()
onDestroy()
onSaveInstanceState()
onLowMemory()
like this:
private MapView mapView;
#Nullable #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// inflate your view `root` and then call ...
mapView = (MapView) root.indViewById(R.id.map_view);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
return root;
}
#Override public void onResume() {
super.onResume();
mapView.onResume();
}
#Override public void onPause() {
super.onPause();
mapView.onPause();
}
#Override public void onDestroyView() {
super.onDestroyView();
mapView.onDestroy();
}
#Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
#Override public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
#Override public void onMapReady (GoogleMap googleMap) {
// from https://developer.android.com/reference/com/google/android/gms/maps/OnMapReadyCallback.html
// and here you can do your map stuff with `googleMap`
}
I am trying to retain fragment when configuration changes but even after using setRetainInstace(true), onDestroy() is called with screen rotation
Here is my fragment code:
public class HelloMoonFragment extends Fragment{
private Button mPlay;
private Button mStop;
private Button mPause;
private AudioPlayer mPlayer = new AudioPlayer();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_hello_moon, null);
mPlay = (Button) v.findViewById(R.id.hellomoon_playButton);
mPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mPlayer.play(view.getContext());
}
});
mStop = (Button) v.findViewById(R.id.hellomoon_stopButton);
mStop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mPlayer.stop();
}
});
mPause = (Button) v.findViewById(R.id.hellomoon_pauseButton);
mPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
mPlayer.playpause();
}catch (IllegalStateException | NullPointerException ex){
mPlayer.play(getActivity());
}
}
});
return v;
}
#Override
public void onDestroyView() {
super.onDestroyView();
Toast.makeText(getActivity(), "Ikkkkkkkkkkkkkkkkkk", Toast.LENGTH_SHORT);
Log.i("HEYEE", "NOOO");
mPlayer.stop();
}
}
and here is my layout xml file:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/helloMoonFragment"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="com.github.domain.hellomoon.HelloMoonFragment">
</fragment>
Why onDestroy() is called on each rotation?
onDestroy() is not going to be called but in your example, it is:
#Override
public void onDestroyView() {
super.onDestroyView();
Toast.makeText(getActivity(), "Ikkkkkkkkkkkkkkkkkk", Toast.LENGTH_SHORT);
Log.i("HEYEE", "NOOO");
mPlayer.stop();
}
OnDestroy!=onDestroyView
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onDestroyView Called when the view previously created by onCreateView(LayoutInflater, ViewGroup, Bundle) has been detached from the fragment
This is as designed. onDestroyView() will be called on a configuration change regardless of the setRetainInstance() flag. However, onDestroy() will not be called.
The reason for this is that the Activity in which the fragment is hosted may still be destroyed and re-created -- therefore, any View references in your Fragment will still be holding a reference to that now-dead Activity. So the expected behavior is that any instance state, running tasks, etc. can continue on properly during a configuration change; however, you are still responsible for releasing your View references and re-creating them in onCreateView().
I had created tabview with swipe using Fragments and FragmentPagerAdapter. In the fragment's hosting activity, I had added a fragment and that fragment shows tabview using TabHost. 1st tab has a listview that displays data from DB using CursorLoader and the 2nd one is mapView.
Everything works fine in portrait mode.
Problem occurs in following case:
User is using app in landscape mode. He opened tabbed view screen. Data is displayed in the listview from the cursor loader. Mapview is also displayed in the second tab. So far everything is working as required.
He left the app as is.(Means, he did not pressed back button, home button or switched to another app)
Screen went off after some time.
When user unlocks the device, my app's tabbed view will be visible again. But,now listview does not show any data and mapview also disappeared.
ClientListFragment
public class ClientListFragment extends SwipeRefreshListFragment {
private static final String TAG = "ClientListFragment";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
setHasOptionsMenu(true);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setColorScheme(R.color.gplus_color_1, R.color.gplus_color_2,
R.color.gplus_color_3, R.color.gplus_color_4);
}
#Override
public void onResume() {
super.onResume();
Log.e(TAG, "onResume");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(TAG, "onActivityCreated");
}
#Override
public void onPause() {
// Log.i(TAG, "onPause");
super.onPause();
}
#Override
public void onStop() {
super.onStop();
Log.e(TAG, "onStop");
}
#Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy");
}
}
Map Fragment
public class ClientMapFragment extends Fragment {
private static final String TAG = "ClientMapFragment";
private GoogleMap googleMap;
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.map_fragment, container, false);
googleMap = ((MapFragment) getFragmentManager().findFragmentById(
R.id.map)).getMap();
googleMap.setMyLocationEnabled(true);
return v;
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onPause() {
super.onPause();
}
}
Activity
public class ClientActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
else
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
setContentView(R.layout.activity_clients);
ClientFragment clientFragment = new ClientFragment();
Bundle bundle = new Bundle();
bundle.putBoolean("ShouldChangeActionBarTitle", true);
clientFragment.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, clientFragment,
getResources().getString(R.string.clients_fragment))
.commit();
}
}
FragmentpagerAdapter
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "TabsPagerAdapter";
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
FragmentManager fm;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
mFragments.add(new ClientListFragment());
mFragments.add(new ClientMapFragment());
}
#Override
public Fragment getItem(int index) {
Log.e(TAG, "getItem");
return mFragments.get(index);
}
#Override
public int getCount() {
return mFragments.size();
}
}
Update 1
So, after doing hours of research I came to a conclusion that, it is problem with fragment's state in the FragmentPagerAdapter. When screen is turned off fragmentpager's saveState is called.. When the device is unlocked again, fragment's will be restored from previous state. I verified that LoaderManager callbacks are called, and data was also fetched from DB. But, didn't appeared on screen.
I didn't even bothered about looking at my activities code at once. Everytime, a new fragment was created in onCreate(). The fix is very simple.
if (savedInstanceState == null) {
Log.e(TAG, "onCreate savedInstanceState == null");
ClientFragment clientFragment = new ClientFragment();
Bundle bundle = new Bundle();
bundle.putBoolean("ShouldChangeActionBarTitle",
shouldChangeActionBarTitle);
clientFragment.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, clientFragment,
getResources().getString(R.string.clients_fragment))
.commit();
}
I been running into an issue with loaders lately. I created a small project that reproduces the issue https://github.com/solcott/loaders-orientation-change
I have a simple Activity that adds a fragment
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
if(mainFragment == null){
Log.w(getClass().getSimpleName(), "creating new Fragment");
mainFragment = new MainFragment();
getFragmentManager().beginTransaction().add(R.id.main_fragment, mainFragment).commit();
}
}
}
In my fragment I start a Loader that just returns an integer that is displayed on the screen. There is also a button that starts a new Activity:
public class MainFragment extends Fragment implements LoaderCallbacks<Integer> {
private static int NEXT_VAL = 0;
TextView text1;
Button button1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(1, null, this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
text1 = (TextView) view.findViewById(android.R.id.text1);
button1 = (Button) view.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), NextActivity.class));
}
});
}
#Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
AsyncTaskLoader<Integer> loader = new AsyncTaskLoader<Integer>(
getActivity()) {
#Override
public Integer loadInBackground() {
return NEXT_VAL++;
}
};
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
text1.setText(data.toString());
}
#Override
public void onLoaderReset(Loader<Integer> loader) {
// TODO Auto-generated method stub
}
}
Up to this point everything works fine I can change the orientation and the integer that is displayed doesn't change. I can even start the new activity and then hit the back button and the integer displayed doesn't change.
However, when I navigate to the new Activity, change orientation and press the back button the integer is incremented. I would expect it to behave the same way that it did without the orientation change.
Calling setRetainInstance(true) in Fragment.onCreate() make it even worse. onLoadComplete is never called and the integer is not displayed at all.
Has anyone else run into this issue and found a workaround?
Same question as asked here: Loader unable to retain itself during certain configuration change
This is a damn annoying problem with AsyncTask loaders. Although a solution is proposed in that thread, I think the general consensus is to avoid AsyncTaskLoaders.
I have an android Activity with swipe navigation (implemented with ViewPager).
public class UberActivity extends FragmentActivity {
ViewPager mViewPager;
protected void onCreate(Bundle savedInstanceState) {
//... some stuff
myfragment1=new MyFragment1();
myfragment2=new MyFragment2();
myfragment3=new MyFragment3();
myfragment4=new MyFragment4();
}
public void onChoiceSelected(){
mViewPager.post(new Runnable(){public void run(){
myfragmen1.update();
myfragmen2.update();
myfragmen3.update();
myfragmen4.update();
}});
}
}
public class Fragment4 extends Fragment {
View v;
#Override
public View onCreateView(LayoutInflater layoutInflater, ViewGroup container,
Bundle savedInstanceState) {
v=layoutInflater.inflate(R.layout.mylayout,container,false);
return v;
}
public void update(){
((TextView)v.findViewById(R.id.textView1)).setText("new text");
}
I will get a NullPointerException on update(), unless I beforehand swipe to it (so that its layout is actually inflated before calling update()).
The problem here is that Fragment4 is instanced but its OnCreateView() is not called. How should I check if OnCreateView() is called? I could put a boolean there, but I don't think this is a best practice...
Just add an if-statement around 'v', checking if it is null or not.
public void update(){
if (v != null) {
((TextView)v.findViewById(R.id.textView1)).setText("new text");
}
}