I've been having some issues with some fragments of mine and the code they call, which is causing some lag/stutter and was wondering if you might have some advice or insight for me!
I have an app with 4 Fragments and a MainActivity. 2 of my Fragments (called "Locations" and "Food") have LocationRequest code, including getting the user's Location, connecting the GoogleApiClient, etc, in order to obtain the distance between the user's geographical location and the destination of a specific app-defined location.
It would appear, from observing Logcat, that whenever these 2 fragments are more than one swipe away within the ViewPager, and need to be created again, they are calling all the LocationRequest code again. Now this isn't the actual issue I'm having. My issue occurs whenever the fragments call these requests, as they repopulate TextViews within their ListViews with any new information based on the Location data retrieved - and this results in some noticeable lag/stutter in the UI, which I assume is occurring as the TextViews are being updated with any new location data.
So, my question to you is,
- How would you deal with this issue when moving to a Fragment that needs to be recreated and calls code that causes UI lag?
Can something be done to to make it so that those Fragments that are further than one swipe away are not removed and therefore do not need to be recreated and call all their Location code again?
Can I modify the app to hold more Fragments so all 4 of mine are created at app launch and don't need to load in any data that might cause stutter?
Should I move my Location-related code to my MainActivity instead of having them held within the Fragments? If that were the case, how would I reference ListViews and Adapters used within my Fragments from the MainActivity (as they are essential to my app)?
I hope this wall of text wasn't too much - hopefully some of you had the patience to stick around and read it all!
Finally, I'll show one of my Fragments and also my MainActivity below.
Locations Fragment:
public class Locations extends Fragment implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener {
public Locations() {
// Required empty public constructor
}
private static final int PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 100;
private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
private static final String LOG_TAG = Locations.class.getName();
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
Location mLocation = new Location(LocationManager.NETWORK_PROVIDER);
private ListView mListView;
public List<Word> locations = new ArrayList<>();
// If the app already has the permission to access the user's location at high accuracy
// (fine location), then connect to the GoogleApiClient.
// If not, no connection-handling methods will be called.
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_ACCESS_FINE_LOCATION: {
if (ContextCompat.checkSelfPermission(getActivity(),
ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mGoogleApiClient.connect();
}
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.word_list, container, false);
mListView = (ListView) rootView.findViewById(R.id.list);
// The following code checks if the app has permission to access the user's fine location,
// and requests the permission if necessary:
if (ContextCompat.checkSelfPermission(getActivity(), ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
ACCESS_FINE_LOCATION)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new MaterialStyledDialog.Builder(getActivity())
.setTitle(R.string.title_location_permission)
.setDescription(R.string.text_location_permission)
.setIcon(R.drawable.ic_place_white_48dp)
.withDialogAnimation(true)
.setPositiveText(android.R.string.ok)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
//Prompt the user once explanation has been shown
ActivityCompat.requestPermissions(getActivity(),
new String[]{ACCESS_FINE_LOCATION},
PERMISSION_REQUEST_ACCESS_FINE_LOCATION);
}
})
.setNegativeText(android.R.string.no)
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(getActivity(), new String[]{ACCESS_FINE_LOCATION},
PERMISSION_REQUEST_ACCESS_FINE_LOCATION);
}
}
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(10 * 1000) // 10 seconds, in milliseconds
.setFastestInterval(1 * 1000); // 1 second, in milliseconds\
locations.add(new Word("Bristol", R.drawable.bristol,
41.674009, -71.279006));
locations.add(new Word("Warren", R.drawable.warren_harbor,
41.730647, -71.282688));
locations.add(new Word("Newport", R.drawable.newport_breakers,
41.486677, -71.315144));
locations.add(new Word("Jamestown", R.drawable.jamestown,
41.496313, -71.368435));
locations.add(new Word("Beavertail Lighthouse", R.drawable.beavertail,
41.458054, -71.395744));
locations.add(new Word("Providence", R.drawable.providence,
41.830279, -71.414955));
locations.add(new Word("Roger Williams Park", R.drawable.roger_williams_park,
41.785836, -71.418525));
locations.add(new Word("Colt State Park", R.drawable.colt_state_park,
41.682970, -71.297362));
locations.add(new Word("Blithewold", R.drawable.blithewold,
41.6540652,-71.2681633));
locations.add(new Word("Narragansett", R.drawable.narragansett,
41.433179,-71.457148));
locations.add(new Word("Barrington", R.drawable.barrington_town_hall,
41.740674, -71.308610));
ViewCompat.setNestedScrollingEnabled(mListView, true);
mListView.setAdapter(new LocationsAdapter(getActivity(), locations, mLocation));
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
// If the app already has the permission to access the user's location at high accuracy
// (fine location), then connect to the GoogleApiClient.
if (ContextCompat.checkSelfPermission(getActivity(),
ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED || mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
super.onStart();
}
@Override
public void onResume() {
// If the app already has the permission to access the user's location at high accuracy
// (fine location), then connect to the GoogleApiClient.
if (ContextCompat.checkSelfPermission(getActivity(),
ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
mGoogleApiClient.connect();
}
super.onResume();
// Instantiate the locations ArrayList.
locations = new ArrayList<>();
}
@Override
public void onPause() {
super.onPause();
// If the user has paused the app and the GoogleApiClient is currently connected,
// remove location updating, and disconnect from the GoogleApiClient.
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,
this);
mGoogleApiClient.disconnect();
locations.clear();
}
}
public void onStop() {
// If the user has stopped the app, disconnect from the GoogleApiClient.
mGoogleApiClient.disconnect();
super.onStop();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
Log.i(LOG_TAG, "Location services connected.");
mLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (mLocation == null) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest, this);
} else {
handleNewLocation(mLocation);
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(LOG_TAG, "Location services suspended. Please reconnect.");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(getActivity(),
CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else {
Log.i(LOG_TAG, "Location services connection failed with code " +
connectionResult.getErrorCode());
}
}
@Override
public void onLocationChanged(Location location) {
handleNewLocation(location);
}
private void handleNewLocation(Location location) {
Log.i("TEST handleNewLocation", "Called handleNewLocation");
// Returns the current adapter in use by the ListView supplied (mListView).
LocationsAdapter adapter = (LocationsAdapter) mListView.getAdapter();
adapter.setCurrentLocation(location);
adapter.notifyDataSetChanged();
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
CategoryAdapter adapter = new CategoryAdapter(this, getSupportFragmentManager());
viewPager.setAdapter(adapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
int[] imageResId = {
R.drawable.ic_place_white_48dp,
R.drawable.ic_restaurant_white_48dp,
R.drawable.ic_wallpaper_white_48dp,
R.drawable.ic_account_balance_white_48dp};
// Fragment/Tab Titles
final String[] mTitleNames = {"Locations", "Food", "Photos", "History"};
tabLayout.setupWithViewPager(viewPager);
for (int i = 0; i < imageResId.length; i++) {
tabLayout.getTabAt(i).setIcon(imageResId[i]);
}
// Default actionbar title
setActionBarTitle(mTitleNames[0]);
// Set actionbar title when tab is selected
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int pos = tab.getPosition();
setActionBarTitle(mTitleNames[pos]);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
public void setActionBarTitle(String title) {
getSupportActionBar().setTitle(title);
}
}
Considering the above, is it possible to move all the Location-request, GoogleApiClient (etc) code from the Fragment and put it into the MainActivity instead?
Wouldn't this mitigate the stutter issue, as the MainActivity is always visible and the code would only be called again when onResume() was called if the app was closed and reopened? If this is possible, and the proper solution (you tell me!), how would I go about moving all the code? I would still need to access the ListViews' adapters from both the Locations fragment, as well as the Food fragment - which are called within private void handleNewLocation(Location location)
. Specifically, I'd need to be able to modify this code within handleNewLocation
so that it still can access the specific ListView and Adapter that each of the 2 Fragments use:
LocationsAdapter adapter = (LocationsAdapter) mListView.getAdapter();
adapter.setCurrentLocation(location);
adapter.notifyDataSetChanged();
and
FoodAdapter adapter = (FoodAdapter) mListView.getAdapter();
adapter.setCurrentLocation(location);
adapter.notifyDataSetChanged();
Thanks so much for sticking with this long post of mine and thanks for any help!
UPDATE:
So I tried out the method @DanielNugent pointed out and am having some issues (due to my lack of know-how). I tried to follow the information given in the link, but I think my issue is just not knowing how to properly implement the methods... I'm sure I'll get it at some point! Anyway, below...
I moved all the Location-getting code into my MainActivity and tried to make references from the MainActivity back to the Locations and Food fragments - as shown below in the onLocationChanged()
method now in the MainActivity:
@Override
public void onLocationChanged(Location location) {
FragmentManager fmA = getSupportFragmentManager();
Locations fragmentA = (Locations)fmA.findFragmentByTag("locationsFragment");
fragmentA.handleNewLocation(mLocation);
FragmentManager fmB = getSupportFragmentManager();
Locations fragmentB = (Locations)fmB.findFragmentByTag("foodFragment");
fragmentB.handleNewLocation(mLocation);
}
handleNewLocation()
is still residing within the 2 fragments, and I tried having it accessed/called within the MainActivity by the above block of code.
handleNewLocation() is still the same, but I will list it below for reference...
public void handleNewLocation(Location location) {
mLocation = location;
Log.i("TEST handleNewLocation", "Called handleNewLocation");
// Returns the current adapter in use by the ListView supplied (mListView).
LocationsAdapter adapter = (LocationsAdapter) mListView.getAdapter();
adapter.setCurrentLocation(location);
adapter.notifyDataSetChanged();
}
In each Fragment, I added this code in the onCreateView()
below...
Locations fragment example:
Locations fragment;
if (savedInstanceState != null) {
fragment = (Locations) getFragmentManager()
.findFragmentByTag("locationsFragment");
} else {
fragment = new Locations();
fragment.setArguments(getActivity().getIntent().getExtras());
getFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment, "locationsFragment")
.commit();
}
I also created a method with which to call in the Fragments so that they can receive the Location information that the MainActivity has gathered:
public void getLocation(Location location) {
mLocation = location;
}
Which I then call in the Fragments as ((MainActivity)getActivity()).getLocation(mLocation);
So running this code, I get NullPointerException: Attempt to invoke virtual method 'void com.example.android.visitri.Locations.handleNewLocation(android.location.Location)' on a null object reference
Any ideas? I also am not sure that my code is in proper order at all - probably not... Any thoughts on proper implementation? Thanks so much!