I am reframing my last question, which is unanswered, and I have rewritten the problem following Google's BasicLocation.
My main activity is defined as:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
// private LocationCallback locationCallback;
// private FusedLocationProviderClient mFusedLocationClient;
private FusedLocationProviderClient mFusedLocationClient;
protected Location mLastLocation;
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
private static final String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawer_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
ImageButton leftNav = findViewById(R.id.left_nav);
ImageButton rightNav = findViewById(R.id.right_nav);
leftNav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int tab = viewPager.getCurrentItem();
if (tab > 0) {
tab--;
viewPager.setCurrentItem(tab);
} else if (tab == 0) {
viewPager.setCurrentItem(tab);
}
}
});
rightNav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int tab = viewPager.getCurrentItem();
tab++;
viewPager.setCurrentItem(tab);
}
});
}
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
getLastLocation();
}
}
with latlang.[Lat,Lang] is in a seperate file:
public class latlang {
public static double Lat;
public static double Lang;
}
and the location file, which is the first fragment in the viewpager is defined as:
public class SunFragment extends Fragment {
List<SunSession> sunsList;
Typeface sunfont;
//to be called by the MainActivity
public SunFragment() {
// Required empty public constructor
}
// Keys for storing activity state.
// private static final String KEY_CAMERA_POSITION = "camera_position";
private static final String KEY_LOCATION_NAME = "location_name";
public String location;//="No location name found";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retrieve location and camera position from saved instance state.
if (savedInstanceState != null) {
location = savedInstanceState.getCharSequence(KEY_LOCATION_NAME).toString();
System.out.println("OnCreate location "+location);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_sun, container, false);
onSaveInstanceState(new Bundle());
//SecondFragment secondFragment = new SecondFragment();
//secondFragment.getDeviceLocation();
RecyclerView rv = rootView.findViewById(R.id.rv_recycler_view);
rv.setNestedScrollingEnabled(false);
rv.setHasFixedSize(true);
//MyAdapter adapter = new MyAdapter(new String[]{"Today", "Golden Hour", "Blue Hour", "Civil Twilight", "Nautical Twilight", "Astronomical Twilight", "Hello", "World"});
//rv.setAdapter(adapter);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
rv.setLayoutManager(llm);
System.out.println("location "+location);
/*
Reversegeocoding location
*/
String location="No location name found";
String errorMessage = "";
List<Address> addresses = null;
Geocoder geocoder = new Geocoder(getContext(), Locale.getDefault());
try {
addresses = geocoder.getFromLocation(
latlang.Lat,
latlang.Lang,
1);
} catch (IOException ioException) {
// Catch network or other I/O problems.
errorMessage = getString(R.string.service_not_available);
// Log.e(TAG, errorMessage, ioException);
if (getView() != null){
Snackbar.make(getView(), errorMessage, Snackbar.LENGTH_LONG).show();
}
} catch (IllegalArgumentException illegalArgumentException) {
// Catch invalid latitude or longitude values.
errorMessage = getString(R.string.invalid_lat_long_used);
if (getView() != null){
Snackbar.make(getView(),
"Illegal Latitude = " + latlang.Lat + ", Longitude = " +
latlang.Lang, Snackbar.LENGTH_LONG).show();
}
}
if (addresses == null || addresses.size() == 0) {
if (errorMessage.isEmpty()) {
System.out.println("Adress Empty No Address Found");// Snackbar.LENGTH_LONG).show();
location = "Lat:"+latlang.Lat+" Lang: "+latlang.Lang;
}
} else {
location = addresses.get(0).getAddressLine(0);//+", "+ addresses.get(0).getLocality();
/* for(int i = 0; i <= addresses.get(0).getMaxAddressLineIndex(); i++) {
location = addresses.get(0).getAddressLine(i);
}*/
}
The problem with this is evident from the logcat:
I/System.out: location null
I/Google Maps Android API: Google Play services package version: 17785022
I/Choreographer: Skipped 31 frames! The application may be doing too much work on its main thread.
I/System.out: Position:0
I/System.out: Position:1
I/System.out: Position:2
I/zygote: Do full code cache collection, code=202KB, data=177KB
I/zygote: After code cache collection, code=129KB, data=91KB
I/zygote: JIT allocated 56KB for compiled code of void android.view.View.<init>(android.content.Context, android.util.AttributeSet, int, int)
I/zygote: Background concurrent copying GC freed 44415(2MB) AllocSpace objects, 7(136KB) LOS objects, 49% free, 3MB/6MB, paused 294us total 102.458ms
I/System.out: Position:3
I/System.out: Position:4
I/zygote: Do partial code cache collection, code=193KB, data=126KB
I/zygote: After code cache collection, code=193KB, data=126KB
I/zygote: Increasing code cache capacity to 1024KB
I/zygote: JIT allocated 71KB for compiled code of void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
I/zygote: Compiler allocated 4MB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
E/MainActivity: Latit: 37.42342342342342
This shows, at the start, location is null,
I/System.out: location null
then the recyclerview of the sunfragment is created
I/System.out: Position:0
I/System.out: Position:1
I/System.out: Position:2
and after that I am getting the location:
E/MainActivity: Latit: 37.42342342342342
Link of the complete code:https://drive.google.com/file/d/1pMl_3Lf76sy82C0J4b-9ta4jbSHonJ2y/view?usp=sharing
Is it somehow possible to get the location first before creating the sunfragment's oncreateview?
I found something wrong about your code (I may be wrong):
Why fields of latlang are static? It doesn't looks like they should.
At SunFragment.onCreate() you are reading location if savedInstanceState != null. savedInstanceState is not null only if activity that holds this fragment was restored from saved state. It may not happen at all.
You should use fragment's arguments (Bundle) to pass initial data to fragment
You should implement Parcelable interface for latlang to be able to pass custom class thru Bundle
I think that's not everything but for me it seems like enough for this code to not work as you expected
As stated by the previous answer there are a lot of issues in your code.
Apart from that understand that last known location may not always return a value. Gps basically has two data types: Ephemeris (precise nav data) and Almanac (coarse data). Now when the receiver is cold started ie after gps has been off for more than 8-10 mins, there is basically no last known location (the duration may vary based on the device but the basic idea is this).
So when you do not get last known location, fetch the actual live location using the fused client. Also since you are saving the data in shared preference and fetching it in your fragment, i believe your fragment is going to heavily rely on this data. So i would suggest either of the following two approaches to get correct result
1) do not fetch the location in the nesting activity at all. Just do it in the fragment where it is needed. This will not work if other fragments in your viewpager also need the location.
2) If you must have the location in your activity and it is a dependency in the container fragments, you can use two approaches here as well. My approaches rely on event bus.. Event bus, otto or rxbus anything will do
2a) do not add anything to the viewpager. basically fetch the location first fully and then add stuff to the viewpager once you get the location callback.
2b) Add stuff to the viewpager from the start. In the activity once you get the location, use the event bus to inform the fragments of the same and on getting the event in the fragments, actually start what you need to do.
I have previously used both these approaches and everything works. Now it is entirely up to your use case to use what suits you. Either ways it is too long a code and too complicated to post everything here.
Right now you are using like this:
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
getLastLocation();
}
}
In your else statement check if getLastLocation() is not null. if not null, replace the fragment.
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
if(getLastLocation() != null){
//replace your fragment
}else{//Null Location}
}
}
Use a service class to handle location service precisely. Here, i'm giving you a custom LocationService class which i used in many projects to collect the location data from background continuously
LocationService.kt
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
class LocationService : Service() {
//Custom Location Binder class
inner class LocationBinder : Binder() {
val locationService: LocationService
get() = this#LocationService
}
companion object {
val TAG = "LocationService_123"
private val UPDATE_INTERVAL = (4 * 1000).toLong() /* 4 secs */
private val FASTEST_INTERVAL: Long = 2000 /* 2 sec */
private var INSTANCE: LocationService? = null
fun isInstanceCreated(): Boolean {
return (INSTANCE != null)
}
}
private var mFusedLocationClient: FusedLocationProviderClient? = null
private var mLocationListener: LocationListener? = null
private var mLocationBinder = LocationBinder()
fun setLocationListener(locationListener: LocationListener) {
this.mLocationListener = locationListener
}
override fun onBind(intent: Intent): IBinder? {
return mLocationBinder
}
override fun onCreate() {
INSTANCE = this
super.onCreate()
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
if (Build.VERSION.SDK_INT >= 26) {
val CHANNEL_ID = "ostad_gps"
val channel = NotificationChannel(
CHANNEL_ID,
"Ostad GPS",
NotificationManager.IMPORTANCE_DEFAULT)
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("").build()
startForeground(1, notification)
}
}
override fun onDestroy() {
INSTANCE = null
super.onDestroy()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: called.")
getLocation()
return Service.START_NOT_STICKY
}
private fun getLocation() {
// ---------------------------------- LocationRequest ------------------------------------
// Create the location request to start receiving updates
val mLocationRequestHighAccuracy = LocationRequest()
mLocationRequestHighAccuracy.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationRequestHighAccuracy.interval = UPDATE_INTERVAL
mLocationRequestHighAccuracy.fastestInterval = FASTEST_INTERVAL
// new Google API SDK v11 uses getFusedLocationProviderClient(this)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "getLocation: stopping the location mitchService.")
stopSelf()
return
}
Log.d(TAG, "getLocation: getting location information.")
mFusedLocationClient!!.requestLocationUpdates(
mLocationRequestHighAccuracy, object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
val location = locationResult!!.lastLocation
Log.d(TAG, "onLocationResult: got location result: $location")
if (location != null) {
if (mLocationListener != null)
mLocationListener!!.onLocationChanged(location)
}
}
},
Looper.myLooper()
) // Looper.myLooper tells this to repeat forever until thread is destroyed
}
}
add this in your AndroidManifest file and start the LocationService from you MainActivity.
By looking at the code, it seems you do not need a very accurate location, you will be fine with last known location. This value might be null in some cases, like you have already experienced. Simple answer to your question is no, you cannot get not null location before creating SunFragment. Following steps is to load location in background and update UI once found.
Request last known location in MainActivity
Keep a reference of last location in cache for easy loading and better user
experience
If last location is null, request location updates until you get a good fix
Have a listener in SunFragment to track location updates
Here are some code you need (Please do read them)
Use the LocationUtil to handle location related events (I prefer LocationManager over FusedLocationProviderClient);
public class LocationUtil {
public static void updateLastKnownLocation(Context context) {
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if(hasSelfPermission(context, new String[]{
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION})) {
try {
Location currentBestLocation;
Location gpsLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Location lbsLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(isBetterLocation(lbsLocation, gpsLocation)) {
currentBestLocation = lbsLocation;
} else {
currentBestLocation = gpsLocation;
}
if(currentBestLocation == null) {
requestLocationUpdates(lm);
} else {
updateCacheLocation(currentBestLocation);
}
} catch (SecurityException se) {
// unlikely as permission checks
se.printStackTrace();
} catch (Exception e) {
// unexpected
e.printStackTrace();
}
}
}
private static void updateCacheLocation(Location location) {
if(location == null) return;
LocationLite temp = new LocationLite();
temp.lat = location.getLatitude();
temp.lon = location.getLongitude();
Gson gson = new Gson();
String locationString = gson.toJson(temp);
AppCache.setLastLocation(locationString);
}
#SuppressLint("MissingPermission")
private static void requestLocationUpdates(LocationManager lm) {
try {
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0F, new LocationListener() {
#Override
public void onLocationChanged(Location location) {
updateCacheLocation(location);
lm.removeUpdates(this);
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
// doing nothing
}
#Override
public void onProviderEnabled(String s) {
// doing nothing
}
#Override
public void onProviderDisabled(String s) {
// doing nothing
}
});
}catch (Exception e) {
e.printStackTrace();
}
}
private static boolean isBetterLocation(Location location, Location currentBestLocation) {
int TWO_MINUTES = 1000 * 60 * 2;
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
if (location == null) {
// A new location is always better than no location
return false;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
private static boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
public static boolean hasSelfPermission(Context context, String[] permissions) {
// Below Android M all permissions are granted at install time and are already available.
if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
return true;
}
// Verify that all required permissions have been granted
for (String permission : permissions) {
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
Use AppCache to store last location;
public class AppCache {
public static final String KEY_LAST_LOCATION = "_key_last_location";
private static SharedPreferences mPreference;
static {
mPreference = PreferenceManager.getDefaultSharedPreferences(App.getApp().getApplicationContext());
}
public static String getLastLocation() {
return mPreference.getString(KEY_LAST_LOCATION, null);
}
public static String getLastLocation(String defaultValue) {
return mPreference.getString(KEY_LAST_LOCATION, defaultValue);
}
public static void setLastLocation(String lastLocation) {
mPreference.edit().putString(KEY_LAST_LOCATION, lastLocation).commit();
}
public static void registerPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
mPreference.registerOnSharedPreferenceChangeListener(listener);
}
public static void unregisterPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
mPreference.unregisterOnSharedPreferenceChangeListener(listener);
}
}
Put the following code into your MainActivity onCreate() This will call locationManager to get last known location and update app cache.
LocationUtil.updateLastKnownLocation(MainActivity.this);
Also replace fetchLocation(); in onRequestPermissionsResult method with above line of code, so it will look like;
#Override
public void onRequestPermissionsResult(...){
switch (requestCode) {
case 101:
{
...
// permission was granted
//fetchLocation();
LocationUtil.updateLastKnownLocation(MainActivity.this);
} else {
// Show some error
}
return;
}
}
}
I did not use your latlang class. (Please make sure all class names follow Java coding standards) Instead use LocationLite to store location in cache. Also I used GSON google library to convert and restore pojo to JSON and backward.
public class LocationLite {
public double lat;
public double lon;
public String address;
}
Final changes in SunFragment.
Make SunAdapter as a member variable, and SharedPreferences.OnSharedPreferenceChangeListener to listen to any changes on location value.
SunAdapter mAdapter;
SharedPreferences.OnSharedPreferenceChangeListener mPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(AppCache.KEY_LAST_LOCATION.equalsIgnoreCase(key)) {
// location value has change, update data-set
SunSession sunSession = sunsList.get(0);
sunSession.setId(sharedPreferences.getString(key, "No Location"));
sunsList.add(0, sunSession);
mAdapter.notifyDataSetChanged();
}
}
};
Start listening to preference changes in onStart() and unregister in onStop()
#Override
public void onStart() {
super.onStart();
AppCache.registerPreferenceChangeListener(mPreferenceChangeListener);
}
#Override
public void onStop() {
super.onStop();
AppCache.unregisterPreferenceChangeListener(mPreferenceChangeListener);
}
Finally when populating first SunSession use the following instead location local variable. So It will look like following;
sunsList.add(
new SunSession(
AppCache.getLastLocation("Searching location..."),
"",
sun_rise,
"",
sun_set,
"",
moon_rise,
"",
moon_set));
That's all. Feel free to ask anything you do not understand.
Related
I am reframing my last question, which is unanswered, and I have rewritten the problem following Google's BasicLocation.
My main activity is defined as:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
// private LocationCallback locationCallback;
// private FusedLocationProviderClient mFusedLocationClient;
private FusedLocationProviderClient mFusedLocationClient;
protected Location mLastLocation;
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
private static final String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawer_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
ImageButton leftNav = findViewById(R.id.left_nav);
ImageButton rightNav = findViewById(R.id.right_nav);
leftNav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int tab = viewPager.getCurrentItem();
if (tab > 0) {
tab--;
viewPager.setCurrentItem(tab);
} else if (tab == 0) {
viewPager.setCurrentItem(tab);
}
}
});
rightNav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int tab = viewPager.getCurrentItem();
tab++;
viewPager.setCurrentItem(tab);
}
});
}
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
getLastLocation();
}
}
with latlang.[Lat,Lang] is in a seperate file:
public class latlang {
public static double Lat;
public static double Lang;
}
and the location file, which is the first fragment in the viewpager is defined as:
public class SunFragment extends Fragment {
List<SunSession> sunsList;
Typeface sunfont;
//to be called by the MainActivity
public SunFragment() {
// Required empty public constructor
}
// Keys for storing activity state.
// private static final String KEY_CAMERA_POSITION = "camera_position";
private static final String KEY_LOCATION_NAME = "location_name";
public String location;//="No location name found";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retrieve location and camera position from saved instance state.
if (savedInstanceState != null) {
location = savedInstanceState.getCharSequence(KEY_LOCATION_NAME).toString();
System.out.println("OnCreate location "+location);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_sun, container, false);
onSaveInstanceState(new Bundle());
//SecondFragment secondFragment = new SecondFragment();
//secondFragment.getDeviceLocation();
RecyclerView rv = rootView.findViewById(R.id.rv_recycler_view);
rv.setNestedScrollingEnabled(false);
rv.setHasFixedSize(true);
//MyAdapter adapter = new MyAdapter(new String[]{"Today", "Golden Hour", "Blue Hour", "Civil Twilight", "Nautical Twilight", "Astronomical Twilight", "Hello", "World"});
//rv.setAdapter(adapter);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
rv.setLayoutManager(llm);
System.out.println("location "+location);
/*
Reversegeocoding location
*/
String location="No location name found";
String errorMessage = "";
List<Address> addresses = null;
Geocoder geocoder = new Geocoder(getContext(), Locale.getDefault());
try {
addresses = geocoder.getFromLocation(
latlang.Lat,
latlang.Lang,
1);
} catch (IOException ioException) {
// Catch network or other I/O problems.
errorMessage = getString(R.string.service_not_available);
// Log.e(TAG, errorMessage, ioException);
if (getView() != null){
Snackbar.make(getView(), errorMessage, Snackbar.LENGTH_LONG).show();
}
} catch (IllegalArgumentException illegalArgumentException) {
// Catch invalid latitude or longitude values.
errorMessage = getString(R.string.invalid_lat_long_used);
if (getView() != null){
Snackbar.make(getView(),
"Illegal Latitude = " + latlang.Lat + ", Longitude = " +
latlang.Lang, Snackbar.LENGTH_LONG).show();
}
}
if (addresses == null || addresses.size() == 0) {
if (errorMessage.isEmpty()) {
System.out.println("Adress Empty No Address Found");// Snackbar.LENGTH_LONG).show();
location = "Lat:"+latlang.Lat+" Lang: "+latlang.Lang;
}
} else {
location = addresses.get(0).getAddressLine(0);//+", "+ addresses.get(0).getLocality();
/* for(int i = 0; i <= addresses.get(0).getMaxAddressLineIndex(); i++) {
location = addresses.get(0).getAddressLine(i);
}*/
}
The problem with this is evident from the logcat:
I/System.out: location null
I/Google Maps Android API: Google Play services package version: 17785022
I/Choreographer: Skipped 31 frames! The application may be doing too much work on its main thread.
I/System.out: Position:0
I/System.out: Position:1
I/System.out: Position:2
I/zygote: Do full code cache collection, code=202KB, data=177KB
I/zygote: After code cache collection, code=129KB, data=91KB
I/zygote: JIT allocated 56KB for compiled code of void android.view.View.<init>(android.content.Context, android.util.AttributeSet, int, int)
I/zygote: Background concurrent copying GC freed 44415(2MB) AllocSpace objects, 7(136KB) LOS objects, 49% free, 3MB/6MB, paused 294us total 102.458ms
I/System.out: Position:3
I/System.out: Position:4
I/zygote: Do partial code cache collection, code=193KB, data=126KB
I/zygote: After code cache collection, code=193KB, data=126KB
I/zygote: Increasing code cache capacity to 1024KB
I/zygote: JIT allocated 71KB for compiled code of void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
I/zygote: Compiler allocated 4MB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
E/MainActivity: Latit: 37.42342342342342
This shows, at the start, location is null,
I/System.out: location null
then the recyclerview of the sunfragment is created
I/System.out: Position:0
I/System.out: Position:1
I/System.out: Position:2
and after that I am getting the location:
E/MainActivity: Latit: 37.42342342342342
Link of the complete code:https://drive.google.com/file/d/1pMl_3Lf76sy82C0J4b-9ta4jbSHonJ2y/view?usp=sharing
Is it somehow possible to get the location first before creating the sunfragment's oncreateview?
I found something wrong about your code (I may be wrong):
Why fields of latlang are static? It doesn't looks like they should.
At SunFragment.onCreate() you are reading location if savedInstanceState != null. savedInstanceState is not null only if activity that holds this fragment was restored from saved state. It may not happen at all.
You should use fragment's arguments (Bundle) to pass initial data to fragment
You should implement Parcelable interface for latlang to be able to pass custom class thru Bundle
I think that's not everything but for me it seems like enough for this code to not work as you expected
As stated by the previous answer there are a lot of issues in your code.
Apart from that understand that last known location may not always return a value. Gps basically has two data types: Ephemeris (precise nav data) and Almanac (coarse data). Now when the receiver is cold started ie after gps has been off for more than 8-10 mins, there is basically no last known location (the duration may vary based on the device but the basic idea is this).
So when you do not get last known location, fetch the actual live location using the fused client. Also since you are saving the data in shared preference and fetching it in your fragment, i believe your fragment is going to heavily rely on this data. So i would suggest either of the following two approaches to get correct result
1) do not fetch the location in the nesting activity at all. Just do it in the fragment where it is needed. This will not work if other fragments in your viewpager also need the location.
2) If you must have the location in your activity and it is a dependency in the container fragments, you can use two approaches here as well. My approaches rely on event bus.. Event bus, otto or rxbus anything will do
2a) do not add anything to the viewpager. basically fetch the location first fully and then add stuff to the viewpager once you get the location callback.
2b) Add stuff to the viewpager from the start. In the activity once you get the location, use the event bus to inform the fragments of the same and on getting the event in the fragments, actually start what you need to do.
I have previously used both these approaches and everything works. Now it is entirely up to your use case to use what suits you. Either ways it is too long a code and too complicated to post everything here.
Right now you are using like this:
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
getLastLocation();
}
}
In your else statement check if getLastLocation() is not null. if not null, replace the fragment.
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
if(getLastLocation() != null){
//replace your fragment
}else{//Null Location}
}
}
Use a service class to handle location service precisely. Here, i'm giving you a custom LocationService class which i used in many projects to collect the location data from background continuously
LocationService.kt
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
class LocationService : Service() {
//Custom Location Binder class
inner class LocationBinder : Binder() {
val locationService: LocationService
get() = this#LocationService
}
companion object {
val TAG = "LocationService_123"
private val UPDATE_INTERVAL = (4 * 1000).toLong() /* 4 secs */
private val FASTEST_INTERVAL: Long = 2000 /* 2 sec */
private var INSTANCE: LocationService? = null
fun isInstanceCreated(): Boolean {
return (INSTANCE != null)
}
}
private var mFusedLocationClient: FusedLocationProviderClient? = null
private var mLocationListener: LocationListener? = null
private var mLocationBinder = LocationBinder()
fun setLocationListener(locationListener: LocationListener) {
this.mLocationListener = locationListener
}
override fun onBind(intent: Intent): IBinder? {
return mLocationBinder
}
override fun onCreate() {
INSTANCE = this
super.onCreate()
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
if (Build.VERSION.SDK_INT >= 26) {
val CHANNEL_ID = "ostad_gps"
val channel = NotificationChannel(
CHANNEL_ID,
"Ostad GPS",
NotificationManager.IMPORTANCE_DEFAULT)
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("").build()
startForeground(1, notification)
}
}
override fun onDestroy() {
INSTANCE = null
super.onDestroy()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: called.")
getLocation()
return Service.START_NOT_STICKY
}
private fun getLocation() {
// ---------------------------------- LocationRequest ------------------------------------
// Create the location request to start receiving updates
val mLocationRequestHighAccuracy = LocationRequest()
mLocationRequestHighAccuracy.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationRequestHighAccuracy.interval = UPDATE_INTERVAL
mLocationRequestHighAccuracy.fastestInterval = FASTEST_INTERVAL
// new Google API SDK v11 uses getFusedLocationProviderClient(this)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "getLocation: stopping the location mitchService.")
stopSelf()
return
}
Log.d(TAG, "getLocation: getting location information.")
mFusedLocationClient!!.requestLocationUpdates(
mLocationRequestHighAccuracy, object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
val location = locationResult!!.lastLocation
Log.d(TAG, "onLocationResult: got location result: $location")
if (location != null) {
if (mLocationListener != null)
mLocationListener!!.onLocationChanged(location)
}
}
},
Looper.myLooper()
) // Looper.myLooper tells this to repeat forever until thread is destroyed
}
}
add this in your AndroidManifest file and start the LocationService from you MainActivity.
By looking at the code, it seems you do not need a very accurate location, you will be fine with last known location. This value might be null in some cases, like you have already experienced. Simple answer to your question is no, you cannot get not null location before creating SunFragment. Following steps is to load location in background and update UI once found.
Request last known location in MainActivity
Keep a reference of last location in cache for easy loading and better user
experience
If last location is null, request location updates until you get a good fix
Have a listener in SunFragment to track location updates
Here are some code you need (Please do read them)
Use the LocationUtil to handle location related events (I prefer LocationManager over FusedLocationProviderClient);
public class LocationUtil {
public static void updateLastKnownLocation(Context context) {
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if(hasSelfPermission(context, new String[]{
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION})) {
try {
Location currentBestLocation;
Location gpsLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Location lbsLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(isBetterLocation(lbsLocation, gpsLocation)) {
currentBestLocation = lbsLocation;
} else {
currentBestLocation = gpsLocation;
}
if(currentBestLocation == null) {
requestLocationUpdates(lm);
} else {
updateCacheLocation(currentBestLocation);
}
} catch (SecurityException se) {
// unlikely as permission checks
se.printStackTrace();
} catch (Exception e) {
// unexpected
e.printStackTrace();
}
}
}
private static void updateCacheLocation(Location location) {
if(location == null) return;
LocationLite temp = new LocationLite();
temp.lat = location.getLatitude();
temp.lon = location.getLongitude();
Gson gson = new Gson();
String locationString = gson.toJson(temp);
AppCache.setLastLocation(locationString);
}
#SuppressLint("MissingPermission")
private static void requestLocationUpdates(LocationManager lm) {
try {
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0F, new LocationListener() {
#Override
public void onLocationChanged(Location location) {
updateCacheLocation(location);
lm.removeUpdates(this);
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
// doing nothing
}
#Override
public void onProviderEnabled(String s) {
// doing nothing
}
#Override
public void onProviderDisabled(String s) {
// doing nothing
}
});
}catch (Exception e) {
e.printStackTrace();
}
}
private static boolean isBetterLocation(Location location, Location currentBestLocation) {
int TWO_MINUTES = 1000 * 60 * 2;
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
if (location == null) {
// A new location is always better than no location
return false;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
private static boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
public static boolean hasSelfPermission(Context context, String[] permissions) {
// Below Android M all permissions are granted at install time and are already available.
if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
return true;
}
// Verify that all required permissions have been granted
for (String permission : permissions) {
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
Use AppCache to store last location;
public class AppCache {
public static final String KEY_LAST_LOCATION = "_key_last_location";
private static SharedPreferences mPreference;
static {
mPreference = PreferenceManager.getDefaultSharedPreferences(App.getApp().getApplicationContext());
}
public static String getLastLocation() {
return mPreference.getString(KEY_LAST_LOCATION, null);
}
public static String getLastLocation(String defaultValue) {
return mPreference.getString(KEY_LAST_LOCATION, defaultValue);
}
public static void setLastLocation(String lastLocation) {
mPreference.edit().putString(KEY_LAST_LOCATION, lastLocation).commit();
}
public static void registerPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
mPreference.registerOnSharedPreferenceChangeListener(listener);
}
public static void unregisterPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
mPreference.unregisterOnSharedPreferenceChangeListener(listener);
}
}
Put the following code into your MainActivity onCreate() This will call locationManager to get last known location and update app cache.
LocationUtil.updateLastKnownLocation(MainActivity.this);
Also replace fetchLocation(); in onRequestPermissionsResult method with above line of code, so it will look like;
#Override
public void onRequestPermissionsResult(...){
switch (requestCode) {
case 101:
{
...
// permission was granted
//fetchLocation();
LocationUtil.updateLastKnownLocation(MainActivity.this);
} else {
// Show some error
}
return;
}
}
}
I did not use your latlang class. (Please make sure all class names follow Java coding standards) Instead use LocationLite to store location in cache. Also I used GSON google library to convert and restore pojo to JSON and backward.
public class LocationLite {
public double lat;
public double lon;
public String address;
}
Final changes in SunFragment.
Make SunAdapter as a member variable, and SharedPreferences.OnSharedPreferenceChangeListener to listen to any changes on location value.
SunAdapter mAdapter;
SharedPreferences.OnSharedPreferenceChangeListener mPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(AppCache.KEY_LAST_LOCATION.equalsIgnoreCase(key)) {
// location value has change, update data-set
SunSession sunSession = sunsList.get(0);
sunSession.setId(sharedPreferences.getString(key, "No Location"));
sunsList.add(0, sunSession);
mAdapter.notifyDataSetChanged();
}
}
};
Start listening to preference changes in onStart() and unregister in onStop()
#Override
public void onStart() {
super.onStart();
AppCache.registerPreferenceChangeListener(mPreferenceChangeListener);
}
#Override
public void onStop() {
super.onStop();
AppCache.unregisterPreferenceChangeListener(mPreferenceChangeListener);
}
Finally when populating first SunSession use the following instead location local variable. So It will look like following;
sunsList.add(
new SunSession(
AppCache.getLastLocation("Searching location..."),
"",
sun_rise,
"",
sun_set,
"",
moon_rise,
"",
moon_set));
That's all. Feel free to ask anything you do not understand.
I have a FusedLocationProviderClient that requests locations every 2 minutes. We've had the issue for ages now that in a certain circumstance users would not receive location updates and due to how we handled it in the app, we effectively locked the user out of a major section of the app.
After revisiting the topic again, I found that according to the developer guide, the cache of locations is cleared is you disable GPS.
Fine and dandy, so next time the user visits the app, you'd expect that the cache is cleared, it takes a little longer for the client to get an update and you're good to go again. Sadly, that's not the case. I disabled gps at 15:55, left it off until 16:00, reenabled it at 16:00 and still haven't gotten an update at 16:40
I figured that I might be getting an exception somewhere inside the request-task, so I added all 4 callbacks (onFailure, onCanceled, onSuccess, onComplete) and got onSuccess and onComplete signalling me that nothing's amiss.
I'm using play-services-base 16.0.1 and play-services-location 16.0.0. I have granted the permission ACCESS_FINE_LOCATION and I definitely started my request for location updates using FusedLocationProviderClient::requestLocationUpdates
here is my code
public class LocationHandler extends LocationCallback {
private static final String APPSTART_LOCATION = "APPSTART_LOCATION";
private static final String MOST_RECENT_LOCATION = "MOST_RECENT_LOCATION";
private final static int GPS_TIMEOUT = 3000;
private final Context context;
private final PreferencesHandler preferences;
private final Bus eventBus;
private BehaviorRelay<Location> subject;
private FusedLocationProviderClient client;
private boolean isRequestingLocations = false;
private Disposable timerDisposable;
public LocationHandler(Context context, PreferencesHandler preferences, Bus eventBus) {
this.context = context;
this.preferences = preferences;
this.eventBus = eventBus;
subject = BehaviorRelay.create();
client = new FusedLocationProviderClient(context);
}
#SuppressLint("MissingPermission")
public void connect() {
if (!isRequestingLocations && hasRequiredPermissions()) {
client.requestLocationUpdates(createLocationRequest(), this, null)
.addOnFailureListener(e -> Timber.e(e, "onFailure"))
.addOnCanceledListener(() -> Timber.d("onCanceled"))
.addOnSuccessListener(aVoid -> Timber.d("onSuccess"))
.addOnCompleteListener(task -> Timber.d("onComplete"));
isRequestingLocations = true;
//start a timer so the client will act like nothing's amiss if he didn't receive a location-update within the given timeout
timerDisposable = Observable.timer(GPS_TIMEOUT, TimeUnit.MILLISECONDS).subscribe(ignored -> {
//the reason why this happens is because the provider just uses cached locations. When the user deactivates GPS, the cache is cleared
//once you reactivate gps it should technically work, but it takes a long time nonetheless
ReportingUtil.trackFbEvent(new GPS_NO_LOCATION_RECEIVED((int) TimeUnit.MILLISECONDS.toSeconds(GPS_TIMEOUT)));
preferences.setHasGeolocation(true);
eventBus.post(new NoLocationReceivedEvent());
eventBus.post(new FirstLocationArrivedEvent());
});
}
}
private LocationRequest createLocationRequest() {
LocationRequest request = new LocationRequest();
long interval = TimeUnit.MINUTES.toMillis(Constants.GPSINTERVAL);
request.setInterval(interval);
request.setFastestInterval(interval);
request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
return request;
}
public void disconnect() {
if (isRequestingLocations) {
timerDisposable.dispose();
client.removeLocationUpdates(this);
isRequestingLocations = false;
}
}
public Observable<Location> getAllLocations() {
return subject;
}
#Override
public void onLocationResult(LocationResult result) {
Timber.d("onLocationResult: %s", result);
if (result != null && result.getLocations().size() != 0) {
Location location = result.getLocations().get(0);
if (location != null) {
if (timerDisposable != null) {
timerDisposable.dispose();
}
subject.accept(new Location(location));
}
}
}
private boolean hasRequiredPermissions() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
public void restart() {
disconnect();
connect();
}
}
I want to query parse to return list of names stored on the Parse Cloud. I am implemented geoLocation and I am using getLatitude and getLongitutude to ParseGeoPoint to get the list of names.
public class TakePhotoActivity extends BaseActivity implements RevealBackgroundView.OnStateChangeListener, LocationListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
CameraHostProvider {
public static final String ARG_REVEAL_START_LOCATION = "reveal_start_location";
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final int STATE_TAKE_PHOTO = 0;
private static final int STATE_SETUP_PHOTO = 1;
/*
* Define a request code to send to Google Play services This code is returned in
* Activity.onActivityResult
*/
private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
/*
* Constants for location update parameters
*/
// Milliseconds per second
private static final int MILLISECONDS_PER_SECOND = 1000;
// The update interval
private static final int UPDATE_INTERVAL_IN_SECONDS = 5;
// A fast interval ceiling
private static final int FAST_CEILING_IN_SECONDS = 1;
// Update interval in milliseconds
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = MILLISECONDS_PER_SECOND
* UPDATE_INTERVAL_IN_SECONDS;
// A fast ceiling of update intervals, used when the app is visible
private static final long FAST_INTERVAL_CEILING_IN_MILLISECONDS = MILLISECONDS_PER_SECOND
* FAST_CEILING_IN_SECONDS;
#Bind(R.id.vRevealBackground)
RevealBackgroundView vRevealBackground;
#Bind(R.id.vPhotoRoot)
View vTakePhotoRoot;
#Bind(R.id.vShutter)
View vShutter;
#Bind(R.id.ivTakenPhoto)
ImageView ivTakenPhoto;
#Bind(R.id.vUpperPanel)
ViewSwitcher vUpperPanel;
#Bind(R.id.vLowerPanel)
ViewSwitcher vLowerPanel;
#Bind(R.id.cameraView)
CameraView cameraView;
#Bind(R.id.rvFilters)
RecyclerView rvFilters;
#Bind(R.id.btnTakePhoto)
Button btnTakePhoto;
private float radius;
private float lastRadius;
private boolean pendingIntro;
private int currentState;
private Location lastLocation;
private Location currentLocation;
// A request to connect to Location Services
private LocationRequest locationRequest;
// Stores the current instantiation of the location client in this object
private GoogleApiClient locationClient;
private File photoPath;
public static void startCameraFromLocation(int[] startingLocation, Activity startingActivity) {
Intent intent = new Intent(startingActivity, TakePhotoActivity.class);
intent.putExtra(ARG_REVEAL_START_LOCATION, startingLocation);
startingActivity.startActivity(intent);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);
updateStatusBarColor();
updateState(STATE_TAKE_PHOTO);
setupRevealBackground(savedInstanceState);
setupPhotoFilters();
radius = InstaMaterialApplication.getSearchDistance();
lastRadius = radius;
// Create a new global location parameters object
locationRequest = LocationRequest.create();
// Set the update interval
locationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
// Use high accuracy
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the interval ceiling to one minute
locationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS);
// Create a new location client, using the enclosing class to handle callbacks.
locationClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
vUpperPanel.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vUpperPanel.getViewTreeObserver().removeOnPreDrawListener(this);
pendingIntro = true;
vUpperPanel.setTranslationY(-vUpperPanel.getHeight());
vLowerPanel.setTranslationY(vLowerPanel.getHeight());
return true;
}
});
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void updateStatusBarColor() {
if (Utils.isAndroid5()) {
getWindow().setStatusBarColor(0xff111111);
}
}
private void setupRevealBackground(Bundle savedInstanceState) {
vRevealBackground.setFillPaintColor(0xFF16181a);
vRevealBackground.setOnStateChangeListener(this);
if (savedInstanceState == null) {
final int[] startingLocation = getIntent().getIntArrayExtra(ARG_REVEAL_START_LOCATION);
vRevealBackground.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vRevealBackground.getViewTreeObserver().removeOnPreDrawListener(this);
vRevealBackground.startFromLocation(startingLocation);
return true;
}
});
} else {
vRevealBackground.setToFinishedFrame();
}
}
FiltersQueryAdapter mainAdapter = new FiltersQueryAdapter(this, PhotoFiltersAdapter.class
, new ParseRecyclerQueryAdapter.QueryFactory() {
public ParseQuery create() {
Location myLoc = (currentLocation == null) ? lastLocation : currentLocation;
ParseQuery query = ParseQuery.getQuery("PlaceFilters");
//query.include("user");
query.orderByAscending("GeoArea");
query.whereWithinKilometers("GeoArea", geoPointFromLocation(myLoc), radius);
query.setLimit(6);
return query;
}
});
private void setupPhotoFilters() {
rvFilters.setHasFixedSize(true);
rvFilters.setAdapter(mainAdapter);
rvFilters.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
}
#Override
protected void onResume() {
super.onResume();
cameraView.onResume();
// Get the latest search distance preference
radius = InstaMaterialApplication.getSearchDistance();
// Checks the last saved location to show cached data if it's available
if (lastLocation != null) {
// If the search distance preference has been changed, move
// map to new bounds.
if (lastRadius != radius) {
// Save the current radius
lastRadius = radius;
doListQuery();
}
}
}
#Override
protected void onPause() {
super.onPause();
cameraView.onPause();
}
/*
* Called when the Activity is no longer visible at all. Stop updates and disconnect.
*/
#Override
public void onStop() {
// If the client is connected
if (locationClient.isConnected()) {
stopPeriodicUpdates();
}
// After disconnect() is called, the client is considered "dead".
locationClient.disconnect();
super.onStop();
}
/*
* Called when the Activity is restarted, even before it becomes visible.
*/
#Override
public void onStart() {
super.onStart();
// Connect to the location services client
locationClient.connect();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Choose what to do based on the request code
switch (requestCode) {
// If the request code matches the code sent in onConnectionFailed
case CONNECTION_FAILURE_RESOLUTION_REQUEST:
switch (resultCode) {
// If Google Play services resolved the problem
case Activity.RESULT_OK:
if (InstaMaterialApplication.APPDEBUG) {
// Log the result
Log.d(InstaMaterialApplication.APPTAG, "Connected to Google Play services");
}
break;
// If any other result was returned by Google Play services
default:
if (InstaMaterialApplication.APPDEBUG) {
// Log the result
Log.d(InstaMaterialApplication.APPTAG, "Could not connect to Google Play services");
}
break;
}
// If any other request code was received
default:
if (InstaMaterialApplication.APPDEBUG) {
// Report that this Activity received an unknown requestCode
Log.d(InstaMaterialApplication.APPTAG, "Unknown request code received for the activity");
}
break;
}
}
#OnClick(R.id.btnTakePhoto)
public void onTakePhotoClick() {
btnTakePhoto.setEnabled(false);
cameraView.takePicture(true, true);
animateShutter();
}
#OnClick(R.id.btnAccept)
public void onAcceptClick() {
PublishActivity.openWithPhotoUri(this, Uri.fromFile(photoPath));
}
/*
* Verify that Google Play services is available before making a request.
*
* #return true if Google Play services is available, otherwise false
*/
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
if (InstaMaterialApplication.APPDEBUG) {
// In debug mode, log the status
Log.d(InstaMaterialApplication.APPTAG, "Google play services available");
}
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Display an error dialog
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0);
if (dialog != null) {
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
errorFragment.setDialog(dialog);
errorFragment.show(getSupportFragmentManager(), InstaMaterialApplication.APPTAG);
}
return false;
}
}
/*
* Called by Location Services when the request to connect the client finishes successfully. At
* this point, you can request the current location or start periodic updates
*/
public void onConnected(Bundle bundle) {
if (InstaMaterialApplication.APPDEBUG) {
Log.d("Location Connected", InstaMaterialApplication.APPTAG);
}
currentLocation = getLocation();
startPeriodicUpdates();
}
/*
* Called by Location Services if the connection to the location client drops because of an error.
*/
public void onDisconnected() {
if (InstaMaterialApplication.APPDEBUG) {
Log.d("Location Disconnected", InstaMaterialApplication.APPTAG);
}
}
#Override
public void onConnectionSuspended(int i) {
Log.i(InstaMaterialApplication.APPTAG, "GoogleApiClient connection has been suspend");
}
/*
* Called by Location Services if the attempt to Location Services fails.
*/
public void onConnectionFailed(ConnectionResult connectionResult) {
// Google Play services can resolve some errors it detects. If the error has a resolution, try
// sending an Intent to start a Google Play services activity that can resolve error.
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException e) {
if (InstaMaterialApplication.APPDEBUG) {
// Thrown if Google Play services canceled the original PendingIntent
Log.d(InstaMaterialApplication.APPTAG, "An error occurred when connecting to location services.", e);
}
}
} else {
// If no resolution is available, display a dialog to the user with the error.
showErrorDialog(connectionResult.getErrorCode());
}
}
/*
* Report location updates to the UI.
*/
public void onLocationChanged(Location location) {
currentLocation = location;
if (lastLocation != null
&& geoPointFromLocation(location)
.distanceInKilometersTo(geoPointFromLocation(lastLocation)) < 0.01) {
// If the location hasn't changed by more than 10 meters, ignore it.
return;
}
lastLocation = location;
// Update map radius indicator
doListQuery();
}
/*
* In response to a request to start updates, send a request to Location Services
*/
private void startPeriodicUpdates() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(
locationClient, locationRequest, this);
}
/*
* In response to a request to stop updates, send a request to Location Services
*/
private void stopPeriodicUpdates() {
locationClient.disconnect();
}
/*
* Get the current location
*/
private Location getLocation() {
// If Google Play Services is available
if (servicesConnected()) {
// Get the current location
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
}
return LocationServices.FusedLocationApi.getLastLocation(locationClient);
} else {
return null;
}
}
/*
* Set up a query to update the list view
*/
private void doListQuery() {
Location myLoc = (currentLocation == null) ? lastLocation : currentLocation;
// If location info is available, load the data
if (myLoc != null) {
// Refreshes the list view with new data based
// usually on updated location data.
// mainAdapter.;
}
}
/*
* Helper method to get the Parse GEO point representation of a location
*/
private ParseGeoPoint geoPointFromLocation(Location loc) {
return new ParseGeoPoint(loc.getLatitude(), loc.getLongitude());
}
/*
* Show a dialog returned by Google Play services for the connection error code
*/
private void showErrorDialog(int errorCode) {
// Get the error dialog from Google Play services
Dialog errorDialog =
GooglePlayServicesUtil.getErrorDialog(errorCode, this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment in which to show the error dialog
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(getSupportFragmentManager(), InstaMaterialApplication.APPTAG);
}
}
/*
* Define a DialogFragment to display the error dialog generated in showErrorDialog.
*/
public static class ErrorDialogFragment extends DialogFragment {
// Global field to contain the error dialog
private Dialog mDialog;
/**
* Default constructor. Sets the dialog field to null
*/
public ErrorDialogFragment() {
super();
mDialog = null;
}
/*
* Set the dialog to display
*
* #param dialog An error dialog
*/
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
/*
* This method must return a Dialog to the DialogFragment.
*/
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
}
private void animateShutter() {
vShutter.setVisibility(View.VISIBLE);
vShutter.setAlpha(0.f);
ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(vShutter, "alpha", 0f, 0.8f);
alphaInAnim.setDuration(100);
alphaInAnim.setStartDelay(100);
alphaInAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(vShutter, "alpha", 0.8f, 0f);
alphaOutAnim.setDuration(200);
alphaOutAnim.setInterpolator(DECELERATE_INTERPOLATOR);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(alphaInAnim, alphaOutAnim);
animatorSet.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
vShutter.setVisibility(View.GONE);
}
});
animatorSet.start();
}
#Override
public void onStateChange(int state) {
if (RevealBackgroundView.STATE_FINISHED == state) {
vTakePhotoRoot.setVisibility(View.VISIBLE);
if (pendingIntro) {
startIntroAnimation();
}
} else {
vTakePhotoRoot.setVisibility(View.INVISIBLE);
}
}
private void startIntroAnimation() {
vUpperPanel.animate().translationY(0).setDuration(400).setInterpolator(DECELERATE_INTERPOLATOR);
vLowerPanel.animate().translationY(0).setDuration(400).setInterpolator(DECELERATE_INTERPOLATOR).start();
}
#Override
public CameraHost getCameraHost() {
return new MyCameraHost(this);
}
class MyCameraHost extends SimpleCameraHost {
private Camera.Size previewSize;
public MyCameraHost(Context ctxt) {
super(ctxt);
}
#Override
public boolean useFullBleedPreview() {
return true;
}
#Override
public Camera.Size getPictureSize(PictureTransaction xact, Camera.Parameters parameters) {
return previewSize;
}
#Override
public Camera.Parameters adjustPreviewParameters(Camera.Parameters parameters) {
Camera.Parameters parameters1 = super.adjustPreviewParameters(parameters);
previewSize = parameters1.getPreviewSize();
return parameters1;
}
#Override
public void saveImage(PictureTransaction xact, final Bitmap bitmap) {
runOnUiThread(new Runnable() {
#Override
public void run() {
showTakenPicture(bitmap);
}
});
}
#Override
public void saveImage(PictureTransaction xact, byte[] image) {
super.saveImage(xact, image);
photoPath = getPhotoPath();
}
}
private void showTakenPicture(Bitmap bitmap) {
vUpperPanel.showNext();
vLowerPanel.showNext();
ivTakenPhoto.setImageBitmap(bitmap);
updateState(STATE_SETUP_PHOTO);
}
#Override
public void onBackPressed() {
if (currentState == STATE_SETUP_PHOTO) {
btnTakePhoto.setEnabled(true);
vUpperPanel.showNext();
vLowerPanel.showNext();
updateState(STATE_TAKE_PHOTO);
} else {
super.onBackPressed();
}
}
private void updateState(int state) {
currentState = state;
if (currentState == STATE_TAKE_PHOTO) {
vUpperPanel.setInAnimation(this, R.anim.slide_in_from_right);
vLowerPanel.setInAnimation(this, R.anim.slide_in_from_right);
vUpperPanel.setOutAnimation(this, R.anim.slide_out_to_left);
vLowerPanel.setOutAnimation(this, R.anim.slide_out_to_left);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
ivTakenPhoto.setVisibility(View.GONE);
}
}, 400);
} else if (currentState == STATE_SETUP_PHOTO) {
vUpperPanel.setInAnimation(this, R.anim.slide_in_from_left);
vLowerPanel.setInAnimation(this, R.anim.slide_in_from_left);
vUpperPanel.setOutAnimation(this, R.anim.slide_out_to_right);
vLowerPanel.setOutAnimation(this, R.anim.slide_out_to_right);
ivTakenPhoto.setVisibility(View.VISIBLE);
}
}
}
Here is my Logcat:
02-16 17:32:40.700 22343-22343/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.google.peep, PID: 22343
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.google.peep/com.google.peep.activity.TakePhotoActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'double android.location.Location.getLatitude()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2484)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2544)
at android.app.ActivityThread.access$900(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'double android.location.Location.getLatitude()' on a null object reference
at com.google.peep.activity.TakePhotoActivity.geoPointFromLocation(TakePhotoActivity.java:501)
at com.google.peep.activity.TakePhotoActivity.access$300(TakePhotoActivity.java:66)
at com.google.peep.activity.TakePhotoActivity$3.create(TakePhotoActivity.java:223)
at com.javon.parserecyclerviewadapter.ParseRecyclerQueryAdapter.loadParseData(ParseRecyclerQueryAdapter.java:96)
at com.javon.parserecyclerviewadapter.ParseRecyclerQueryAdapter.registerAdapterDataObserver(ParseRecyclerQueryAdapter.java:176)
at android.support.v7.widget.RecyclerView.setAdapterInternal(RecyclerView.java:886)
at android.support.v7.widget.RecyclerView.setAdapter(RecyclerView.java:847)
at com.google.peep.activity.TakePhotoActivity.setupPhotoFilters(TakePhotoActivity.java:232)
at com.google.peep.activity.TakePhotoActivity.onCreate(TakePhotoActivity.java:152)
Your getlocation is before the google api client actually connects,pass setup photofilters in onConnected and it will run.Tatasthu!
I am working on an app that should get accurate user location continuously if user is inside a specific zone.
The location updates should be saved on a server (Parse.com) so other users can get the current location of the user.
The flow is:
Application onCreate -->
start Service with LocationListener -->
onLocationChanged -->
save new location on Parse.com -->
Parse.com Cloud afterSave method send push notification to users -->
other users get notifications via broadcast reciever -->
update user marker on map <--
Questions:
I am not sure about how to implement step 4, should I save the new location on parse inside the onLocationChanged method? should I pass it to a BroadcastReciever and save it on parse there? or should I pass it to a static method in my CurrentUser class that will perform the save on server?
I have noticed the requestLocationUpdates documentation says:
"This method is suited for the foreground use cases, more specifically
for requesting locations while being connected to LocationClient. For
background use cases, the PendingIntent version of the method is
recommended."
In my case, which method should I use?
What values should I use for getting almost real time location without consuming more than 5% of battery power per hour?
My Service Class:
public class UserTracker extends Service implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, LocationListener{
static int GEOFENCE_STATUS;
final static int INSIDE_GEOFENCE = 9001, OUTSIDE_GEOFENCE = 9002;
static final int MINIMUM_ACCURACY = 26;
final static int GEOFENCE_RADIUS = 2000;
static final int NULL_LOCATION_ERROR = 7001;
static final int INSIDE_INTERVAL = 10000, INSIDE_FASTEST_INTERVAL = 1000, INSIDE_SMALLEST_DISPLACEMENT = 10;
static final int OUTSIDE_INTERVAL = 300000, OUTSIDE_FASTEST_INTERVAL = 60000, OUTSIDE_SMALLEST_DISPLACEMENT = 100;
static Location eventLocation;
LocationClient lc;
#Override
public void onCreate() {
super.onCreate();
eventLocation = new Location("Test");
lc = new LocationClient(getApplicationContext(), this, this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
double eventLat = intent.getDoubleExtra("Latitude", -1);
double eventLon = intent.getDoubleExtra("Longitude", -1);
if (eventLat == -1 || eventLon == -1) {
stopSelf();
return START_REDELIVER_INTENT;
}
eventLocation.setLatitude(eventLat);
eventLocation.setLongitude(eventLon);
lc.connect();
return START_STICKY;
}
public int getGeofenceStatus(Location currentLocation) {
if (currentLocation == null) {
return NULL_LOCATION_ERROR;
}
if (currentLocation.distanceTo(eventLocation) > GEOFENCE_RADIUS) {
Log.d(TAG, "User is outside geofence");
return OUTSIDE_GEOFENCE;
} else {
Log.d(TAG, "User is inside geofence");
return INSIDE_GEOFENCE;
}
}
public void requestLocationUpdates(int status) {
GEOFENCE_STATUS = status;
LocationRequest request = LocationRequest.create();
switch (status) {
case NULL_LOCATION_ERROR:
stopSelf();
break;
case INSIDE_GEOFENCE:
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
request.setInterval(INSIDE_INTERVAL);
request.setFastestInterval(INSIDE_FASTEST_INTERVAL);
request.setSmallestDisplacement(INSIDE_SMALLEST_DISPLACEMENT);
break;
case OUTSIDE_GEOFENCE:
request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
request.setInterval(OUTSIDE_INTERVAL);
request.setFastestInterval(OUTSIDE_FASTEST_INTERVAL);
request.setSmallestDisplacement(OUTSIDE_SMALLEST_DISPLACEMENT);
break;
}
lc.requestLocationUpdates(request, this);
}
#Override
public void onLocationChanged(Location location) {
int newStatus = getGeofenceStatus(location);
int accuracy = (int) location.getAccuracy();
String message = "Geofence status: " + newStatus +
"\nAccuracy: " + accuracy +
"\nDistance to event: " + location.distanceTo(eventLocation);
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
if (newStatus == INSIDE_GEOFENCE && accuracy < MINIMUM_ACCURACY) {
Log.d(TAG, "Accuracy is good");
// do the save on server procees
}
if (GEOFENCE_STATUS != newStatus) {
requestLocationUpdates(newStatus);
}
}
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.d(TAG, "LocationClient connection failed: " + result.getErrorCode());
}
#Override
public void onConnected(Bundle arg0) {
Location currentLocation = lc.getLastLocation();
requestLocationUpdates(getGeofenceStatus(currentLocation));
}
#Override
public void onDisconnected() {
Log.d(TAG, "LocationClient disconnected");
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I am starting a service based on Alarm Manager in every 20 seconds which sends GPS data to my server.
The problem is my heap and allocated heap size goes on increasing. When I analysed the heap dump, I found the number of service instances is equal to the number of calls to startService(). How to avoid this issue?
public class SystemBootListener extends BroadcastReceiver {
// Restart service every 30 seconds
private static final long REPEAT_TIME = 1000 * 10;
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, StartLocationServiceAfterReboot.class);
PendingIntent pending = PendingIntent.getBroadcast(context, 0, PendingIntent.FLAG_UPDATE_CURRENT);
// Start 20 seconds after boot completed - so that all providers are initialized by then
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, 20);
// Trigger every 10 seconds
// InexactRepeating allows Android to optimize the energy consumption
AlarmManager service = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
service.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), REPEAT_TIME, pending);
}
}
public class StartLocationServiceAfterReboot extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if(AppSettings.isRouteConfigured(context)){
AppSettings.setServiceRunning(context, Boolean.TRUE);
Intent service = new Intent(context, GPSComputationService.class);
context.startService(service);
}
}
}
public class GPSComputationService extends Service {
private static final int MAX_TIME_TO_FETCH_NEW_LOCATION = 8000;
private final IBinder mBinder = new ServiceBinder();
private Timer timerToFetchLocInfoFromProviders = null;
private LocationManager locationManager = null;
private boolean gpsProviderEnabled=false;
private boolean networkProviderEnabled=false;
private int numberOfSatellites = 0;
private GPSData bestKnownLocation = new GPSData();
private TCPWriter tcpWriter ;
#Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
tcpWriter= new TCPWriter(this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
/*tcpWriter= new TCPWriter(this);*/
computeBestLocation();
return Service.START_STICKY;
}
private void stopGPSComputationService(){
stopSelf();
}
#Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class ServiceBinder extends Binder {
public GPSComputationService getService() {
return GPSComputationService.this;
}
}
public GPSData getBestKnownLocation() {
return bestKnownLocation;
}
public void publishBestKnownLocation(GPSData bestKnownLocation) {
this.bestKnownLocation = bestKnownLocation;
sendBestKnownLocationToNMEAServer();
}
public void sendBestKnownLocationToNMEAServer(){
if(getBestKnownLocation() == null){
stopGPSComputationService();
return;
}
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.getDeviceId();
NMEAData dataPacketToWrite = new NMEAData(
telephonyManager.getDeviceId(),
getBestKnownLocation().getLatitude(),
getBestKnownLocation().getLongitude(),
getBestKnownLocation().getTimeStamp(),
getBestKnownLocation().getSpeed(),
getBestKnownLocation().getNumberOfSatellites()
);
tcpWriter.sendMessage(NMEAServerTypes.MVT600,
dataPacketToWrite);
stopGPSComputationService();
}
public GPSData computeBestLocation() {
Log.d("#############GPSComputation Status", "Running.......");
try{
if(locationManager==null)
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//Add status listener for satellite count
locationManager.addGpsStatusListener(gpsStatusListener);
Criteria criteria = new Criteria();
criteria.setSpeedRequired(true);
criteria.setBearingRequired(true);
List<String> providers = locationManager.getProviders(criteria, false);
//Capture if the GPS/Network providers have been disabled.
try{
gpsProviderEnabled=providers.contains(LocationManager.GPS_PROVIDER) &&
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}catch(Exception e){
}
try{
networkProviderEnabled=providers.contains(LocationManager.NETWORK_PROVIDER) &&
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}catch(Exception e){
}
if(!gpsProviderEnabled && !networkProviderEnabled)
return null;
if(gpsProviderEnabled)
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
if(networkProviderEnabled)
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
timerToFetchLocInfoFromProviders=new Timer();
timerToFetchLocInfoFromProviders.schedule(new GetLastKnownGoodLocation(), MAX_TIME_TO_FETCH_NEW_LOCATION);
locationManager.removeGpsStatusListener(gpsStatusListener);
//Finally store the data in backend Service
return getBestKnownLocation() ;
}catch(Exception e){
return null;
}
}
LocationListener locationListenerGps = new LocationListener() {
public void onLocationChanged(Location location) {
timerToFetchLocInfoFromProviders.cancel();
publishBestKnownLocation(extractAllGeoInfFromLocation(location));
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerNetwork);
locationManager.removeGpsStatusListener(gpsStatusListener);
gpsStatusListener = null;
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
//listen for gps status changes to capture number of satellites.
GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
#Override
public void onGpsStatusChanged(int event) {
if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS || event == GpsStatus.GPS_EVENT_FIRST_FIX) {
GpsStatus status = locationManager.getGpsStatus(null);
Iterable<GpsSatellite> sats = status.getSatellites();
// Check number of satellites in list to determine fix state
int tempNumberOfSatellites = 0;
for (GpsSatellite sat : sats) {
if(sat.usedInFix())
tempNumberOfSatellites++;
}
numberOfSatellites = tempNumberOfSatellites;
}
}
};
LocationListener locationListenerNetwork = new LocationListener() {
public void onLocationChanged(Location location) {
timerToFetchLocInfoFromProviders.cancel();
publishBestKnownLocation(extractAllGeoInfFromLocation(location));
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerGps);
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
class GetLastKnownGoodLocation extends TimerTask {
#Override
public void run() {
locationManager.removeUpdates(locationListenerGps);
locationManager.removeUpdates(locationListenerNetwork);
Location bestKnownNetworkLocation = null, bestKnownGPSLocation=null;
if(gpsProviderEnabled)
bestKnownGPSLocation=locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if(networkProviderEnabled)
bestKnownNetworkLocation=locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(bestKnownGPSLocation!=null && bestKnownNetworkLocation!=null){
if(bestKnownGPSLocation.getTime()>bestKnownNetworkLocation.getTime())
publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownGPSLocation));
else
publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownNetworkLocation));
return;
}
if(bestKnownGPSLocation!=null){
publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownGPSLocation));
return;
}
if(bestKnownNetworkLocation!=null){
publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownNetworkLocation));
return;
}
AppLog.logWarningMsg("Bad luck-NO BEST LOCATION AVAILABLE");
publishBestKnownLocation(null);
}
}
private GPSData extractAllGeoInfFromLocation(Location location){
bestKnownLocation = new GPSData();
bestKnownLocation.setLatitude(location.getLatitude());
bestKnownLocation.setLongitude(location.getLongitude());
bestKnownLocation.setTimeStamp(location.getTime());
bestKnownLocation.setSpeed(location.getSpeed()*3.8);
bestKnownLocation.setNumberOfSatellites(numberOfSatellites);
return bestKnownLocation;
}
}
There is only one instance of service. As per the document
Multiple calls to Context.startService() do result in multiple corresponding calls to onStartCommand()),
But only one service instance can exist.
On startService(), the Android system calls the service's onStartCommand() method. If the service is not already running, the system first calls onCreate(), then calls onStartCommand().
The only thing that can create this kind of scenario is that you have some kind of memory leak.
Your service did it's work and stopped but didn't garbage collected. it probably happen few times and that is why you see many instances of it.
It's hard to find memory leaks but i would suggest you to start from the listeners. check if you unregistered them at the right time.
This link can help you to detect the leak:
https://developer.android.com/studio/profile/am-memory.html
Some tips to improve and simplify your code:
You want to use one shot service to report GPS coordinates. For this purpose IntentService is much better and it runs in a background thread by design.
https://developer.android.com/training/run-background-service/create-service
Use PendingIntent.getService() instead of call StartLocationServiceAfterReboot which just launch other Android component. You can do that immediately. You save one step.
https://developer.android.com/reference/android/app/PendingIntent.html#getService(android.content.Context,%2520int,%2520android.content.Intent,%2520int)
Every time you use a resource (like GPS, Sensor, etc...) you have to also write the release part. As I see, you register listeners to GPS Service but never release (unregister) them.
What does AppSettings.setServiceRunning(context, Boolean.TRUE);? My guess you save this into SharedPreference. This can be compromised when app force-stopped or device restarted or suddenly shut-down. Maybe better way this https://stackoverflow.com/a/5921190/5823014
Avoid to use static on Context, Activity, Service, BroadcastReceiver, Application instance. I'm not see in your code snippet, just a general advice to prevent memory-leaks.