I've recently tried to simplify some data structures by posting them into a simple key, value map. I push a log to verify the value has been associated to the key during the .put method.
When I later call a .get, the value is no longer available. Important code snippets:
MainActivity.class
public final HashMap<String, Integer> resumeMap = new HashMap<String, Integer>();
if (resumeMap.containsKey(url)) {
Log.i("Value is there!", url);
resumeTime = resumeMap.get(url);
Log.i("Value set to:", "" + resumeTime);
} else {
resumeTime = 0;
Log.i("Value is not found!", "" + url);
}
public void setHashmap(String url, Integer time) {
resumeMap.put(url, time);
int newTime = resumeMap.get(url);
Log.i("Setting:", "URL: " + url);
Log.i("Setting:", "TIME:" + newTime);
}
VideoPlayer.class
MainActivity setter = new MainActivity();
setter.setHashmap(urlString, player.getCurrentPosition());
In the setHashmap method, the log is correctly outputting both url and time as expected. However resumeMap.containsKey(url) is remaining false, even while the debugger is confirming an expected matching key via "Value is not found!" output.
To make clear, during first pass in MainActivity, I expect a not found result, the VideoPlayer class is then called with the resumeTime of 0 with proper results. I verify the setting of the key and value, and when I open the same link I am still receiving a 0.
I originally built the Hashmap in it's own class, with the same results. I moved the map to the MainActivity just to debug. Thank you for any assistance you may be able to provide.
Activity instances are quickly destroyed and re-created all the time, for example, when you rotate the screen (but for other reasons as well). It often appears that the Activity is "still there", but the actual underlying object instance has been destroyed and recreated.
A quick way to verify that that is really the problem before going down the instance state bundle path is to make the HashMap static to prevent instance destruction from destroying it.
Sometimes, static maps like this are OK, but proceed with caution, as structures like this open up avenues for leaking memory all over the place. A better approach is to use some sort of persistence, or if you only need the information while the Activity is being used, pass the information around using onSaveInstanceState and onRestoreInstanceState (see http://developer.android.com/training/basics/activity-lifecycle/recreating.html)
Related
I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
I create an app that like dictionary app. When the user types in an Edittext, I call an AsyncTask to compute and update the result to the screen (I put the UI update code in onPostExecute() method ). However, when you type little fast, the eddittext become not responesive (a little latency). I think this promblem occurs because many AsyncTasks are running (each AsynTask for an input letter). So, I think I need to stop the first task before calling new task. Am I right? What should I do in this situation?
You don't need to implement the filter method in an async task. I call filter method on data when first letter has been written in editbox and save the result in an temporary array, then when another letter has been written, I call filter method on the temporary data which technically has less information than the original data. By doing this, the dimmension of data set decreases as you type in editbox. Also, you can use this method to store previous data set so when you press backspace, you don't have to call filter method again, you just go to previous saved temporary data set. For me, it works fine and I don't have to use async task because it is efficient
I suggest you another approach: use only one thread. The searching thread should wait for searching data > do search > and sleep until new data. E.g.:
private static class SearchThread extends Thread{
private Object monitor = new Object();
private String value;
public void search(String value){
this.value = value;
synchronized (monitor){monitor.notify();}
}
#Override
public void run() {
while(true){
try {System.out.println("Wait for search data."); synchronized (monitor){monitor.wait(); }
} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("Searching for " + value);
}
}
}
Good day, Sorry for the rather long post. this is more like a design decision than anything else. I have an activity which contains 4 fragments. Now i switch through the fragments through a slideMenu.
Each Fragment makes a call to a webservices and gets a JSONObject result vis an AsyncTask. I am looking for a structure where i would have to cache each JSONObject result
for each fragment . when the user switches between the fragments, it checks if a certain amount of time has expired and acts accordingly
-if cached time has expired, reload the network request again.
-if not, use the cached JSONObject result and display to the user.
this means if i have an expiration time for like 6 hrs or rather the data from the backend updates every 6hrs, I should only request from the webservice via the asynctask
once every 6 hours and other times just used the cached values instead.
My current implementation
1. To have a set of global boolean variables via extending the Application class to load for each fragments the first time.
public class MyApp extends Application {
private boolean First_load_frag1 = true;
private boolean First_load_frag2= true;
public void setLoadFrag1(boolean value){
First_load_frag1 = value;
}
public boolean getLoadFrag1(){
return First_load_frag1;
}
}
in my fragments, i set them to true in onCreate(), use it to determine whether to request the first time and then set it to false so i don't have to anymore.
then in my fragments OnResume() i check to see if the cached_time has expired or not. i do something like this
#Override
public void onResume(){
super.onResume();
public final static long MINUTE_MILLIS = 60000;
Calendar cal = Calendar.getInstance();
int mins = (int) ((cal.getTimeInMillis()/MINUTE_MILLIS) - (cached_timer/MINUTE_MILLIS);
if(mins > 180){
Toast.makeText(getActivity(), "on resume refresh data", Toast.LENGTH_LONG).show();
requestService(); //this sends a request to the webservice
} else {
cached_result = manager.getCachedJSONResult(CACHED_RESULT );
if(cached_result != null){
Toast.makeText(getActivity(), "on resume cached data", Toast.LENGTH_LONG).show();
loadCachedResult(cached_result);
}
}
}
This feels like a messy way of handling this situations to me. Taken into consideration application crashes and Activity lifecycles, is this a good solution of doing something like this oris there a more elegant way i can achieve this? Every input will be much appreciated. Thanks
1.first you need to create data structure for holding data for fragment
check in data structure that data is available for selected fragement and it is time for refresh data
if yes then call load data(web service to load data) into fragment
else
load data from data structure and show in fragment.
I want to add a HashMap to a singelton (which will be the application instance object) to pass data between activites. I cant use a Bundle as the objects are too big to be passed through an intent.
I'm currently using this implementation:
public class MyApp extends Application {
private static MyApp singelton;
private static Map<String, Object> tempDataStorage;
#Override
public void onCreate() {
super.onCreate();
singelton = this;
tempDataStorage = new HashMap<String, Object>();
}
// puts the object in the Map and returns the key
public static String putDataInStorage(Object data) {
String key = UUID.randomUUID().toString() // generate a key
tempDataStorage.put(key, data);
}
// gets the object from the Map and deletes it to save memory
public static Object getDataFromStorage(String key) {
Object o = tempDataStorage.get(key);
tempDataStorage.remove(key);
}
}
putDataInStorage() is called from activity A to pass the data to activity B which then calls getDataFromStorage() with the key passed through the intent. If activity B gets destroyed by android, it also calles putDataInStorage() in its onSaveInstanceState() method to reclaim the data later.
Im still having a problem with android destroying my application after some time if the app is not used. If the user then comes back, it seems to recreate Activity B with the Bundle from saveInstanceState, while the new HashMap is empty.
I first thought about writing the Data from the Map into an SQL-Database when the app is destroyed and recreate the objects on recreation. But this would lead into an endlessly growing Map (and therfore endlessly growing memory usage) as the objects would never disappear. Not a good solution though.
As the data is fetched from a webserver, my second idea was to also save a reference to the data in the saveInstanceState() of Activity B (eg. the parameters of the GET-request). If activity B is then confronted with a NullPointerException, it can refetch the data.
Is that a good solution? If not, whats a better one?
Thanks for your help!
You should not count on saving dynamic data. Application can be killed at any time when other apps need memory. Use SharedPreferences to save your data permanently.
So I'm still working on my first little app here, new to Android and Java, so I'm stuck on a basic little problem here. Answers to my first questions were really helpful, so after researching and not coming up with anything, I thought I'd ask for some more help!
The idea is that on another screen the user makes a choice A, B, C, or D, and that choices is passed as a string through the intent. OnResume checks if the choice is not null and sets an integer that corresponds to that string. Later when the user pushes another button, some if else logic checks that int and performs and action based on which was chosen. The problem is that the App crashed at onResume.
I learned that I have to use equals(string) to compare string reference, but maybe the problem is that I am trying to compare a string in reference to a literal string? Any help would be greatly appreciated.
Thanks!
protected void onResume() {
super.onResume();
// Get the message from the intent
Intent intent = getIntent();
String choice = intent
.getStringExtra(ExtensionSetupSlector.TORQUE_SETUP);
// Create the text view
TextView displayChoice = (TextView) findViewById(R.id.displayChoice);
if (!choice.equals("")){
displayChoice.setText(choice);
if (choice.equals("A")) {
myChoice = 1;
}
if (choice.equals("B")) {
myChoice = 2;
}
if (choice.equals("C")) {
myChoice = 3;
}
if (choice.equals("D")) {
myChoice = 4;
}
}
}
myChoice is declare right after ...extends Activity{ Also I'm not quite sure If this should really be in onResume, but it was working before I started try to set myChoice in the onResume (when I was just displaying the choice). Thanks again!
Change if (!choice.equals("")) to check for null instead. Otherwise your app attempts to access an empty reference and crashes.