How to periodically update the location to Firebase Realtime database? - java

I am creating a realtime public transport tracking system using Android Studio and Firebase. I have created a toggle button which the user toggle on, the location will start tracking.
The problem I have faced is the location data only send one time to the database. But for the apps, the location is tracking normally.
What I need is every time the location update have to update into the database. Is that mistake or error in my code?
This is the location callback function:
//Location Callback
locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
if(locationResult == null) {
return;
}
Location driverLocation = locationResult.getLastLocation();
Toast.makeText(DriverDetail.this, driverLocation.getLatitude()+", "+driverLocation.getLongitude()
, Toast.LENGTH_SHORT).show();
Log.d("Location Update: ", "onLocationResult: Location is: "
+driverLocation.getLatitude()+", "+driverLocation.getLongitude());
}
};
This is the code for the Toggle button
locate_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View button) {
if (locate_btn.isChecked()) {
// The toggle is enabled
Toast.makeText(DriverDetail.this, "Start tracking location", Toast.LENGTH_SHORT).show();
final String value_lat = String.valueOf(driverLocation.getLatitude());
final String value_lng = String.valueOf(driverLocation.getLongitude());
getLocationUpdates();
ref.child(firebaseUser.getUid()).child("Latitude").setValue(value_lat);
ref.child(firebaseUser.getUid()).child("Longitude").setValue(value_lng);
}
else {
// The toggle is disabled
fusedLocationProviderClient.removeLocationUpdates(locationCallback);
Toast.makeText(DriverDetail.this, "Stop tracking location", Toast.LENGTH_SHORT).show();
}
}
});
This is the location request that I have set.
//Update Location
private void getLocationUpdates(){
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(3000);
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
}

Your code is right now writing the location data to Firebase when the user toggles the button.
What you instead want is to write the location continuously, once the button has been toggled. This means that the writing should happen (or be triggered from) inside onLocationResult:
locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
if(locationResult == null) {
return;
}
Location driverLocation = locationResult.getLastLocation();
if (true) { // TODO: check if button is toggled
final String value_lat = String.valueOf(driverLocation.getLatitude());
final String value_lng = String.valueOf(driverLocation.getLongitude());
ref.child(firebaseUser.getUid()).child("Latitude").setValue(value_lat);
ref.child(firebaseUser.getUid()).child("Longitude").setValue(value_lng);
}
}
};
In the if (true) statement you might want to check if the button is toggled. But since I noticed that you only register for location updates when the user toggles the button, that check might not even be needed.

Related

Android chat app Checking online/offline users firebase

I am trying to add online/offline feature in my android chat app using .info/connected path
I wrote the following code inside onCreate() method
studentref = FirebaseDatabase.getInstance().getReference("student").child(user.getUid());
connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
studentref.child("status").onDisconnect().setValue("offline");
studentref.child("status").setValue("Online");
} else {
studentref.child("status").setValue("offline");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
But the else part does not execute when i minimized the app for more than 60 seconds
It only works when i killed the app or when i switch off the internet for more than 60 seconds
How to make it works , when the app is in foreground it should set the value "online" and when the app is in background or killed it should set the value to "offline"
I solved this issue using 2 ways
First we need to detect when the app goes to the background and come back to the foreground , so when the app goes to foreground update the user state as "Online"
when the app goes to background update the user state as "Offline"
We can achieve this by implementing LifecycleObserver class and writing the following 2 methods
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onAppBackgrounded() {
//App in background
// here update user state as offline
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onAppForegrounded() {
// App in foreground
// here update user state as online
}
But this will not work when there is no internet connection , Eg: when the user switch off the internet while the app in foreground and close the app in this case the user state will remain online even the app is closed , To solve this we need also to check the connection to .info/connected path
connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
listenerCon = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
user= FirebaseDatabase.getInstance().getReference("users").child(FirebaseAuth.getInstance().getCurrentUser().getUid());
user.child("state").setValue("Online");
user.child("state").onDisconnect().setValue(ServerValue.TIMESTAMP);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
};
connectedRef.addValueEventListener(listenerCon);
So when there is no connection for more than 60 sc the user state will be updated to offline
The .info/connected reflects the connection of your application to the backend servers of the Firebase Realtime Database. While this may have some relation to whether the user is actively using the app, it is not a 1:1 mapping.
If you want to detect whether the app is backgrounded, there are better ways (unrelated to Firebase) such as How to detect when an Android app goes to the background and come back to the foreground and others from these search results.
Use my code, in onCreate
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String id = user.getUid();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference().child("Users").child(id).child("stateOnline");
if (user != null){
reference.setValue(true);
reference.onDisconnect().setValue(false);
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
// CONNECTED
} else {
// NOT CONNECTED
}
}
#Override
public void onCancelled(DatabaseError error) {
System.err.println("Listener was cancelled");
}
});
reference.keepSynced(true);
}

Firebase query inside onMarkerClick is not working properly

I have an activity in which I'm getting the data from Firebase database and showing multiple markers on the map. In onMarkerClick, I want the user to go to another activity that have details about the marker that was tapped. On first tap on the marker, it shows the title only and on second tap it goes to the other activity after doing Firebase database query to get the id for that marker so that the correct details will be provided about the tapped marker.
The problem is when I tap on any marker the first time, and then If I click on any other marker, the opened activity opens up the details of the marker that was tapped first.
If I tap on the same marker twice, it works fine. But when I come back to the map activity, tapping on any marker first time, will open up the details for the marker that was previously tapped.
What can be done to do the correct query inside onMarkerClick.
Here's the code.
#Override
public boolean onMarkerClick(Marker marker) {
eventTitle = marker.getTitle();
q = database.getReference("events")
.orderByChild("event_title")
.equalTo(marker.getTitle());
q.addValueEventListener(vel);
Intent intent = new Intent(NearbyEventsActivity.this, EventDetailActivity.class);
Bundle bundle = new Bundle();
bundle.putString("eventid", eventid);
intent.putExtras(bundle);
if (eventid != null) {
startActivity(intent);
}
return false;
}
ValueEventListener vel = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Event e = snapshot.getValue(Event.class);
eventid = e.getEvent_id();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
The problem is when I tap on any marker the first time, and then If I click on any other marker, the opened activity opens up the details of the marker that was tapped first.
Firebase query executes asyncronously. Your ValueEventListener will not be called immediately.
Use your secound Activity start code inside onDataChange method.
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Event e = snapshot.getValue(Event.class);
eventid = e.getEvent_id();
}
Intent intent = new Intent(NearbyEventsActivity.this, EventDetailActivity.class);
Bundle bundle = new Bundle();
bundle.putString("eventid", eventid);
intent.putExtras(bundle);
if (eventid != null) {
startActivity(intent);
}
}

FusedLocation is not always accurate

I'm developing an app for my work. It's main goal is to verify if workers are in the place they're supposed to be. So, they Check-in at a pint of sale, and immediately a tracking Job is fired, getting user's location every hour. When they leave the place, the job is stopped.
I'm using JobScheduler, as I read this is the best way to handle this, and it's working propertly, so this is fine. The problem is, location is not always accurate, and this is a MUST, because the bosses of the workers need to verify that they are at their point of sale.
There have been many cases when a user is inside the POS, but the app shows like he/she was some meters away from the allowed area. (We are using a 30m radius as the "allowed area"). So, if the app is not showing reliable information, our client won't trust our app anymore.
This is my code (I'm showing just location stuff) I took as reference this google sample. :
//Package and imports...
public class TrackingJob extends JobService{
// Some variables...
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 5000;
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;
private LocationCallback mLocationCallback;
private Location mLastLocation;
private LocationRequest mLocationRequest;
private LocationSettingsRequest mLocationSettingsRequest;
private FusedLocationProviderClient mFusedLocationProviderClient;
#Override
public boolean onStartJob(JobParameters jobParameters) {
createLocationCallback(jobParameters);
createLocationRequest();
buildLocationSettingsRequest();
startLocationServices();
return true; // Async stuff will be done, so true is returned
}
#Override
public boolean onStopJob(JobParameters jobParameters) { return false; }
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
private void createLocationCallback(final JobParameters jobParameters) {
mLocationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
mLastLocation = locationResult.getLastLocation();
// Save data locally, and then, upload it to server
saveLocalData(jobParameters.getExtras(), true);
syncData(jobParameters);
// We stop locationProvider here, because we got a location already
mFusedLocationProviderClient.removeLocationUpdates(mLocationCallback);
}
};
}
private void buildLocationSettingsRequest(){
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
}
#SuppressLint("MissingPermission") // Permissions WILL BE accepted before the job is called
private void startLocationServices() {
SettingsClient mSettingsClient = LocationServices.getSettingsClient(this);
mSettingsClient.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
#Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
mFusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(TrackingJob.this);
// We ask for location updates
mFusedLocationProviderClient.requestLocationUpdates(
mLocationRequest,
mLocationCallback,
Looper.myLooper()
);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// If problem can be solved by user, we send a push noty
}
});
}
private void saveLocalData(PersistableBundle bundle, boolean gps){
// We save data to REALM
}
private void syncData(final JobParameters jobParameters){
// We get data to sync from REALM
// If data was succesfully synced, we delete it from REALM
// We stop the job
jobFinished(jobParameters, false);
}
}
Am I doing something wrong? Is there a better and more accurate way of getting user's location? Is this a "phone-related" problem?
Before using this new FusedLocationProvider, I was using GoogleApiClient connectionCallbacks and LocationListeners, and accuracy was a problem too.

Fetching data from firebase taking more than one button click to display

I want to display data which is fetched from firebase whenever user clicks a button. This is the code which I am using currently
todayPoem = (TextView) findViewById(R.id.todpoem);
/*Firebase related code*/
myFirebaseRef = new Firebase("https://firebase url");
myFirebaseRef.child("poem").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
s1 = (String) snapshot.getValue();
}
#Override
public void onCancelled(FirebaseError error) {
}
});
}
public void onClickPoem(View v) {
todayPoem.setTextSize(20);
todayPoem.setText(s1);
}
And the xml file contains a textview and a button to fetch data from the firebase. But the problem is whenever I click the button the data is not displayed instantly, usually it takes almost 5-6 button clicks for the data to be displayed. What am I doing wrong here?
I had this problem before... I would advice you doing it like this example:
mrf.setAndroidContext(this);
mrf = new Firebase('Firebase url');
final Firebase myData = mrf.child("info");
myData.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
x1 = dataSnapshot.getValue(String.class);
x2 = x1; // Global variable
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
use variable 'x2' to do what you want. I don't why but there is some problem with using the same variable that was used to receive Data from Firebase.
Hope it work with you...
Also if you would like the user to know that data are being retrieve you can use progressBar with Handler. After a button is clicked and 'x1' equals null do something like this: progressBar.setVisibility(View.VISIBLE);
then
handler.postDelayed(new Runnable() {
public void run() {
progressBar.setVisibility(View.GONE);
t.setText(x2);
}
}, 800);
Good luck..

How does Firebase sync work, with shared data?

I use Firebase to handle the Auth topic of my Android app.
I also save a user profile on Firebase, that contain user id and extra options that user can update in the android app.
At startup, the app check the auth, and auth. It reload the user profile (on Firebase) to then update my userInstance on the app.
Firebase is set as offline capable at app level.
The userInstance POJO is sync with Firebase at log and stop to be sync at unlog.
I have a special parameter, in my user profile that I can change (as an admin).
Every-time I update it on the Firebase console, it is replaced by the previous value when the app start again.
How can this happen ?
BTW :
1/ Based on which mechanism are the data merged, if multiple client have different local values ?
Here is simpler code, where I tried to reproduce the error. :
MyApplication.java
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Firebase.setAndroidContext(this);
Firebase.getDefaultConfig().setLogLevel(Logger.Level.DEBUG);
Firebase.getDefaultConfig().setPersistenceEnabled(true);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
Firebase ref;
User user;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ref = new Firebase("https://millezim-test.firebaseIO.com").child("user");
ref.keepSynced(true);
Button br = (Button) findViewById(R.id.b_read);
Button bs = (Button) findViewById(R.id.b_save);
final TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);
user = new User();
bs.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!tv_s.getText().toString().equalsIgnoreCase(""))
user.setAge(Integer.valueOf(tv_s.getText().toString()));
ref.setValue(user);
}
});
br.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue(User.class);
if (u != null)
tv_r.setText(String.valueOf(u.getAge()));
else
tv_r.setText("Bad Value");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
});
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue(User.class);
u.setCounter(u.getCounter() + 1);
user = u;
saveUser();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
public void saveUser() {
ref.setValue(user);
}
}
If you just change a value in the app, then the counter inc until the app stop : this is normal. But what is strand is the age pass from old to new then to old cyclically without stopping.
And I feel that behavior in my app, without the cyclic, as I do not have a counter, but I cannot change a parameter in the admin client, I always get back the previous value stored in the mobile.
I just Auth, then I update my UserInstance with AuthData + the User fetch from Firebase (probably the cached data), and then I save back the updated User under Firebase (As I may got new AuthData, and I normally get the latest User from Firebase)
2/ In this simple example, I saw that if I read the value at start, it fetch the data cached in the app. How can I force having online data ?
The problem comes from the fact that you're using disk persistence with a single-value event listener. When you do:
ref.addListenerForSingleValueEvent(new ValueEventListener() {...
You're asking for a single value of that location (the user in your case). If Firebase has a value for that location in its local cache, it will fire for that value straight away.
The best way to solve this is to not use a single-value listener, but instead use a regular event listener. That way you will get two events: one for the cached version and one for the version that comes back from the server (if it is different).
The only alternative is to not use Firebase's disk persistence. Without that, there won't be a local cache for the data to be read from upon a restart.
There were a few discussions about this combination on the Firebase mailing list. Here's one: https://groups.google.com/forum/#!msg/firebase-talk/ptTtEyBDKls/XbNKD_K8CQAJ
After digesting a bit, my current strategy is to use Firebase for my data persistance, and not use anymore my own objects. (Before I had to sync UI, my data, the firebase cache, and the server data)
So now, I use
use disk caching
use onValueEventListener
keep update data (only to be read with component that need sync data)
trigger event that update UI (for component that can accept async data)
define some specific setter, that update data on Firebase (not anymore at app level)
It does means, that when I update a data, it goes to the server (or Firebase caching layer) until it goes back to the UI. As firebase handle this caching, if fast this is fast enough, and this is Firebase that deal with network sync.
To bring the (1) solution from #frank-van-puffelen into code :
public class MainActivity extends AppCompatActivity {
Firebase ref;
User user;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ref = new Firebase("https://test.firebaseIO.com").child("user");
ref.keepSynced(true);
Button br = (Button) findViewById(R.id.b_read);
Button bs = (Button) findViewById(R.id.b_save);
final TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);
user = new User();
bs.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!tv_s.getText().toString().equalsIgnoreCase(""))
user.setAge(Integer.valueOf(tv_s.getText().toString()));
ref.setValue(user);
}
});
br.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue(User.class);
if (u != null)
tv_r.setText(String.valueOf(u.getAge()));
else
tv_r.setText("Bad Value");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
});
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue(User.class);
u.setCounter(u.getCounter() + 1);
user = u;
saveUser();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
public void saveUser() {
ref.setValue(user);
}
}
However, this change nothing, even worst. Now it seems that every value set, stay as a ghost value (hold by client/server request), and the value toggling can be seen with every values set !
EDIT
The following code worked out !
Having a normal ValueListener, that you stopped before saving again a value, and you enable back when save is completed ! (Ok I was thinking this may be done in the Firebase Framework).
public class MainActivity extends AppCompatActivity {
Firebase ref;
User user;
private ValueEventListener theListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ref = new Firebase("https://test.firebaseIO.com").child("user");
ref.keepSynced(false);
Button bs = (Button) findViewById(R.id.b_save);
final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);
user = new User();
bs.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!tv_s.getText().toString().equalsIgnoreCase(""))
user.setAge(Integer.valueOf(tv_s.getText().toString()));
ref.setValue(user);
}
});
theListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue(User.class);
u.setCounter(u.getCounter() + 1);
user = u;
updateUI(u);
saveUser();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
};
ref.addValueEventListener(theListener);
}
public void saveUser() {
ref.removeEventListener(theListener);
ref.setValue(user, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
ref.addValueEventListener(theListener);
}
});
}
public void updateUI(User user) {
TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
if (user != null)
tv_r.setText(String.valueOf(user.getAge()));
else
tv_r.setText("Bad Value");
}
}
EDIT
However this do not allow to change a value on the admin page. The age value is set and then remain back to the one that is save on the mobile.
So I imaging then the only solution is to solve at system level. DO NOT USE VALUELISTENER FOR VALUE THAT AN APP CAN SAVE AND THAT CAN BE SAVED BY THIRD PARTY APP. Please advise/correct this assumption !

Categories

Resources