I have an array list with a list of 100 records which I fetch during the availability of the internet. When the internet is not available then I am fetching it from the Application Class instance. Here is the method how I am doing it :
At the parsing step(when internet is available, after parsing the data) :
BApplication.bAppSession.setFindBData(findBDataList);
Here is the Application Class:
public class BApplication extends Application {
public static BAppSession bAppSession;
#Override
public void onCreate() {
bAppSession = new BAppSession(getApplicationContext());
}
}
Here is my Session Class:
public class BAppSession {
private Context context;
private ArrayList<FindMyBeerData> findMyBeerDataLast = new ArrayList<FindMyBeerData>();
public BeerAppSession(Context context) {
this.context = context;
}
public void setFindMyBeerData(ArrayList<FindMyBeerData> findMyBeerDataList){
this.findMyBeerDataLast = findMyBeerDataList;
}
public ArrayList<FindMyBeerData> getFindMyBeerData(){
return this.findMyBeerDataLast;
}
}
How to Fetch the Data(Offline):
findMyBDataList = BApplication.bAppSession
.getFindMyBeerData();
Problem : Everything is working fine, but the problem is when I kill the application from the task manager and restart again (when the internet is not available) , I am getting empty ArrayList. DATA IS LOST WHEN APPLICATION IS KILLED
Query: How to get the data back even if the application is killed by the user ? Suggest me any way to do the same.
From this answer - https://stackoverflow.com/a/19335539/816416 - it's clear that there's no event called when an application is killed from the task manager or killed by the system itself.
So, the better way is to save the data whenever you fetch it. This will ensure that your data is safe whether your app is killed or not. Next time when you log in, you can load the saved data.
For storing data, you can use the following:
Database SQLite : Tutorial here
Files
SharedPreferences
If you have an Activity, you can save your data inside the onDestroy() call.
Related
I have created an app which is relied on my local server which fetch profile image and information about user..Code works fine without any problem but when I change my data in the local server (for example profile picture )the updated profile is not reflecting in the application until activity is restarted but this should not be happened because live data should reflect the change immediately as soon as changes occurred in the database.
below is the code of live data class
private MutableLiveData<Profile> profileMutableLiveData;
public void init(String token){
if (profileMutableLiveData!=null){
return;
}
repository=Repository.getInstance();
profileMutableLiveData=repository.getProfile(token);
}
public LiveData<Profile> getProfile(){
return profileMutableLiveData;
}
here is my Repository code
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if (instance==null){
instance=new Repository();
}
return instance;
}
public MutableLiveData<Profile> getProfile(String token){
MutableLiveData<Profile> data=new MutableLiveData<>();
RetrofitApi retrofitApi=RetrofitInstance.getInstance();
Call<Profile> call=retrofitApi.getProfile(token);
call.enqueue(new Callback<Profile>() {
#Override
public void onResponse(Call<Profile> call, Response<Profile> response) {
Profile profile=response.body();
if (response.isSuccessful()){
data.setValue(profile);
}
}
#Override
public void onFailure(Call<Profile> call, Throwable t) {
}
});
return data;
}
}
Code in main activity to observe changes....
actually I am showing profile image in navigation drawer ... like telegram app
viewModelClass = new ViewModelProvider(this).get(ViewModelClass.class);
viewModelClass.init(token);
viewModelClass.getProfile().observe(this, new Observer<Profile>() {
#Override
public void onChanged(Profile profile) {
Picasso.get().load("http://192.168.43.216:8000" + profile.getProfile_photo()).into(profileImage);
fName = profile.getFirst_name();
lName = profile.getLast_name();
image = profile.getProfile_photo();
nameView.setText("Hello " + profile.getFirst_name());
}
});
}
The code is working fine but I want the data must be updated as soon as changes made in my server...
but data is updated when I restart the activity or opening app again after closing the activity...
May be the problem - is that you begin to observe in your activity one instance of MutableLiveData, and then you replace it with another one.
In your ViewModel:
profileMutableLiveData=repository.getProfile(token);
you override it instead of setting new value with "postValue"
In your Repository:
MutableLiveData<Profile> data=new MutableLiveData<>();
you make another instance of LiveData
You can try to change your return value from a Repository to a "Profile" and set it as a new value of MutableLiveData in your ViewModel with "postValue"
UPDATED
I've read your question more carefully. I think my answer above wouldn't give you what you expect (in case you expect Retrofit should update LiveData instantly like ROOM does)
So my thoughts:
You expect too much using LiveData+Retrofit. Just using them doesn't mean you'll get on-line updates of your data on your server. To achieve that you have to change mechanism of your interaction with your server, not just fix few lines in code you've shown.
There is mechanism LiveData+ROOM that works with local DB (Sqlite) in a way, that you expect from LiveData+Retrofit. But there is no magic there. Room is using mechanic, that built-in in Sqlite for notifying (triggering) when there are some changes in DB tables occur. But Retrofit doesn't implement similar mechanism with Rest Api and actually it's not its responsibility.
To achieve what you want you can look at several possibilities:
To use some Cloud Service API, that contains that built-in mechanism for notifying your device when data changes (Firebase, for example)
To implement some kind of periodic synchronisation of your app data with server. After this synchronisation you'll have all data on device and depending on where you put your data you could observe changes with LiveData+Room or FileObserver.
To simplify your case and refresh your data from the server at activity explicitly after click on Button "Refresh" on your activity. In that case you can implement steps that I wrote at first version of my answer.
I'm trying to solve a (hypothetical) concurrency problem in my Android app that uses ORMLite for the database management.
In particular, I have a ContentService class that manages the database, here's some code (simplified to understand the problem):
public ContentManagerImpl(Context context) {
mContext = context;
configure();
// Once configured, get the DatabaseHelper
DatabaseManager.configure(context);
}
private void configure() {
// If needed, unzip a folder with an sqlite file
// representing the database and saves it on device
}
#Override
public void checkForUpdate() {
// Checks if new database version is available
new CheckForUpdateTask(CheckForUpdateTask.CheckForUpdateCallback() {
#Override
public void onCheckForUpdateFinished(boolean updateNeeded) {
if (updateNeeded) {
update();
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
#Override
public void update() {
// Downloads the new database zip, unzip the folder and sava an sqlite file
// representing the database
new UpdateTask(mContext, mMetaData, new UpdateTask.UpdateCallback() {
#Override
public void onUpdateFinished() {
DatabaseManager.refresh();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
This class is created in the onCreate method of my Android Application class.
Since in the app I have only one Activity, the checkForUpdate method is called every time the onResume of that activity is called.
The problem is, sometimes when there's an update needed and the app is resumed (or even when first launched) I get an exception on a database query performed inside the DatabaseManager.configure() and DatabaseManager.refresh() methods.
That's because I try to get the first element of a single-row table but the query returns an empty list. It seems that the database is not ready, or that someone is still writing on it. I've also tried some lock mechanism but without luck.
So, since is the main (UI) thread that overwrites the database file, my questions are:
Can it be a problem to access the database from multiple threads/tasks?
Can I perform database queries on a separated thread if I write the db file on the UI thread?
Thank you all for your support.
I was wondering does it matter where you instantiate adapters dataset with Realm? I like to fetch all the data that any adapter needs in the adapters constructor and thus instantiate the dataset there, but almost all examples I've seen fetch the data beforehand in the activity creating the adapter and then pass it to the adapter as a parameter.
With SQLite this seems even more arbitrary, but since I'm using Realm I need to open a realm connection every time I want to access the database and to keep the data available I need to keep the connection open. Keeping this connection open in the activity seems unnecessary since you might need to make queries in the adapter thus having to open a connection to realm within the adapter anyways.
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
since I'm using Realm I need to open a realm connection every time I want to access the database
Wrong, you just need 1 open instance for that given thread in order to access the database.
Keeping this connection open in the activity "seems unnecessary" since you might need to make queries in the adapter
In which case you can have the activity-level Realm instance as a "scoped dependency", that you can share through the Context via getSystemService() if that's what you like to do.
public class MyActivity extends Activity {
Realm realm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
//...
}
#Override
public void onDestroy() {
if(realm != null) {
realm.close();
}
super.onDestroy();
}
...
#Override
public Object getSystemService(String key) {
if("REALM".equals(key)) {
return realm;
}
return super.getSystemService(key);
}
}
public class MyAdapter extends RecyclerView.Adapter<MyModelViewHolder> {
private final Context context;
Realm realm;
RealmResults<MyModel> results;
private final RealmChangeListener listener = new RealmChangeListener() {
#Override
public void onChange(Object element) {
notifyDataSetChanged();
}
}
public MyAdapter(Context context) {
this.context = context;
//noinspection ResourceType
realm = (Realm)context.getSystemService("REALM");
results = realm.where(MyModel.class).findAll();
results.addChangeListener(listener);
}
...
}
thus having to open a connection to realm within the adapter anyways.
wrong
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
It's because your Adapter, which is just supposed to describe how to show the elements of a dataset, become a God that also determines the data that it must show.
Although to be fair, it's actually harder to externally manage the data-set; something must hold a strong reference to the result set anyways. So when I don't really bother with unit-testability, I do obtain the results inside the Adapter itself. It does work.
I don't understand how is the Parse working?
I download data in parse to my arraylist , but when I show the Pets.size inside (//here) method "done" it will show 4, but when I show pets.size outside the done's method it will show 0?
public class Test extends AppCompatActivity {
ArrayList<Pet> pets;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pets = new ArrayList<>();
ParseQuery<Pet> query = new ParseQuery<>("Pets");
query.findInBackground(new FindCallback<Pet>() {
#Override
public void done(List<Pet> list, ParseException e) {
if (e!=null){
Toast.makeText(Test.this,"Error",Toast.LENGTH_LONG).show();
}
for (Pet pet : list){
Pet newPet = new Pet();
newPet.setName(pet.getName());
newPet.setType(pet.getType());
pets.add(newPet);
}
// here
}
});
Toast.makeText(Test.this,"You have "+pets.size()+" pets",Toast.LENGTH_LONG).show();
}
Here's my Pet class:
#ParseClassName("Pets")
public class Pet extends ParseObject {
public String getName(){
return getString("name");
}
public void setName(String name) {
put("name", name);
}
public String getType(){
return getString("type");
}
public void setType(String type) {
put("type", type);
}
}
And an orther question , what should I do if I wanna save the data in local data?
Explanation:
findInbackground performs an operation to find all ParseObjects in a background thread (outside the main thread, or UI thread). So when it completes in the place where you have the comment
//here
That is when the background thread finishes it's call to find the objects. When you try to look at the size of the array outside that call where it shows size of 0, it is because it reached that point before the background thread finishes it's work (of adding to your array from objects it found).
What is happening is the operation for find() is happening in parallel with your main threads code.
And for your second question, make sure you enableLocalDatastore and then you can pin results from queries to your local cache. This data is stored on the device until the user deletes your app or clears cached data in settings.
Follow this guide to setup local cache Local Datastore with Parse
Note: A solution to your problem for when the background task of finding the pets is complete is to call a method from within the Callback for the findInBackground call that will handle the newly found Pet ParseObjects. Also remember to handle if the query fails either by finding no objects or some failure in connection / timeout.
just calling pet.pin() or pet.pinInBackground(); you can save a parseObject in local storage , to query objects in local storage you need set query.fromPin(true)
https://parse.com/docs/android/guide#objects-the-local-datastore
"done" method fires when the background task ends.
My application works this way:
ListView---->onListItemClick---->detailspage---->backpressed---->goes
back to the list---->click the same item again---->same detailspage
loads again.
The details page gets a lot of data from a server and populate its views.
So, it takes 2-4 secs every time an item is clicked in the listview. I have seen apps where they wont load any data if the same page is called 2nd time.
How can I do that?
Currently in my app its like this:
onCreate call AsyncTask to get data and populate the view
nothing in onResume, onPause, onStart, onStop, onDestroyed
You can keep the data for that domain object in a singleton and then when you enter your details page there are two ways to go.
If for instance you had a list of Person class.
public class Person {
private String name;
private Image img;
...
}
Then you could have a PersonCache that was a singleton caching the data for the last Person selected in your list:
public class PersonCache {
private Person cachedPerson;
private static PersonCache instance;
private PersonCache(){
...
}
public PersonCache getInstance(){
if(instance == null){
instance = new PersonCache();
}
return instance;
}
public Person getCachedPerson(){
return cachedPerson;
}
public void setCachedPerson(Person p){
cachedPerson = p;
}
}
So in onCreate when you finish fetching your JSON data you create a Person object and call setCachedPerson.
If you know that the data in the details page won't have been updated:
In onCreate in details page you check if the object that has been selected is the same as the one cached in your singleton (if the objects have unique ids in your database you can look at those to check if it's the same).
If you don't know whether there's new data:
You can use the If-Modified-Since technique when making your GET request in your AsyncTask.
Basically what you do is add a header parameter
key: If-Modified-Since
value: Sat, 29 Oct 1994 19:43:31 GMT
If the server has no new data it can respond with 304 and send no response body but if it has new data it will respond with 200 and send the data just like normal.
Implementing this would require some implementation on the server side as well.
Here's some more info on the technique:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html (section 14.25)