I'm currently using osmdroid to display current positioning.
Based on the following example i tried to optimize the system a little bit by not constructing the ItemizedOverlay<OverlayItem> and ArrayList<OverlayItem> each time my location is changed, but construct them only once in the constructor, and later on simply add points to my ArrayList variable.
Here's how it looks now:
private void InitializeMarkersOverlay() {
mOverlayItemArrayList = new ArrayList<OverlayItem>();
ItemizedOverlay<OverlayItem> locationOverlay =
new ItemizedIconOverlay<OverlayItem>(this, mOverlayItemArrayList, null);
mMapView.getOverlays().add(locationOverlay);
}
and when a new location arrives:
private void AddPointToOverlay(GeoPoint gPt, boolean bShouldClearList) {
OverlayItem overlayItem = new OverlayItem("", "", gPt);
Drawable markerDrawable = ContextCompat.getDrawable(this, R.drawable.pin);
overlayItem.setMarker(markerDrawable);
// first time initializer
if(bShouldClearList) {
mOverlayItemArrayList.clear();
}
mOverlayItemArrayList.add(overlayItem);
}
Since my mMapView already has a pointer to mOverlayItemArrayList i was hoping that my mapview's layer would be automatically notified regarding the change. but nothing actually happens. Only by recreating the objects, i get to see the pin.
Adding to the list does not work because ItemizedIconOverlay need to do some operations on addition. You can check source code for ItemizedIconOverlay.
You can see there is call to populate() in addItem method (and all other methods which are manipulating with items).
public boolean addItem(final Item item) {
final boolean result = mItemList.add(item);
populate();
return result;
}
But populate() is an implementation detail and is marked as protected so you cannot call it directly.
Correct solution would be:
Don't keep reference to the list but to ItemizedIconOverlay
instance.
Use mLocationOverlay.addItem(overlayItem)
You may need to call mapView.invalidate() after adding new point.
I got it working by accessing the overlay directly from the mapview object, not sure why exactly, as i was hoping mMapView.getOverlays() would hold a reference to the ItemizedIconOverlay and its itimized array
if(mMapView.getOverlays().size() > 0) {
((ItemizedIconOverlay<OverlayItem>)mMapView.getOverlays().get(0)).removeAllItems();
((ItemizedIconOverlay<OverlayItem>)mMapView.getOverlays().get(0)).addItem(overlayItem);
}
}
Related
sorry if this is a convoluted question. Working on creating an app for a college course and I'm running into (what appears to be) a race condition in my OnCreate method.
TL;DR - sometimes my spinner populates and I can get an index from it. Sometimes it's not populated yet when trying to get a specific index. Details and code below.
The app is a "course scheduler" for a college student.
I'm creating an Activity that displays existing course information and allows you to edit it. In the OnCreate method for this Activity, I am filling a spinner for "Mentors" for the course and a spinner for which "Term" the course belongs in. This information is being pulled from a Room DB.
I have a seperate activity for a new course and for editing a course. For the "new course" activity, everything works fine. I getAllMentors() or getAllTerms() successfully and fill the spinner list.
For the "Edit Course" Activity, there's an extra step involved and it seems to be causing me some issues.
When editing a course, I pass the intent from the originating Activity with all the necessary EXTRAS. This is successful.
In OnCreate for EditCourseActivity, I do the following:
I get the mentorID from the EXTRA that's passed in from the originating Activity.
I access my MentorViewModel and call my getAllMentors() method which returns LiveData> of all mentors in the db.
because it returns LiveData, I use an observer and loop through the LiveData adding the Name of each mentor to a List and the
entire mentor to a List.
I populate my spinner with the information in List full of mentor names.
then I do a for loop, looping through List looking for one that has the same id as what I grabbed form the EXTRA in step 1.
If I find a match in that list, I call a getMentorName() method to snag their name as a string.
I have a methond getIndex(spinner, string) that will loop through the provided spinner, trying to find a match for the string that's
passed in (mentors name) that I grabbed that should match the ID of
the mentor assigned to the course. This method returns index location
of the matched string in the spinner.
I set the spinner selection to the index found.
I do basically the same process for term.
Me being a new developer, I'm not used to OnCreate running the code synchronously.
Because of this, it appears that I have a race condition somewhere between populating the List of mentor names that populates the spinner, and calling my getIndex() method.
Sometimes the spinner is populated and getIndex works properly and sets the correct mentor. Sometimes the spinner is empty and my getIndex() returns -1 (which it should do in a no-find situation) that populates the spinner with the first item in the list (once it's populated).
protected void onCreate(Bundle savedInstanceState) {
//////////////////////////Handling Mentor spinner menu/////////////////////////////////////////////////
int mentorId = courseData.getIntExtra(EXTRA_COURSE_MENTOR_ID, -1);
final ArrayAdapter<String> sp_CourseMentorAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mentorNameList);
sp_CourseMentorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp_CourseMentor.setAdapter(sp_CourseMentorAdapter);
final MentorViewModel mentorViewModel = ViewModelProviders.of(this).get(MentorViewModel.class);
//Mentor test = mentorViewModel.getMentorById(mentorId);
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
}
sp_CourseMentorAdapter.notifyDataSetChanged();
}
});
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
Is there a way to get them to run asynchronously? Somehow to get the observer doing my getAllMentors() to complete first and populate the spinner, THEN have the for loop run?
Or a better way to handle this?
Thanks in advance.
Room always runs the code on a separated thread, not the Main/UI thread. You can change that behavior with
allowMainThreadQueries()
after initializating your database. This will make the query run first, populate your list and then run your for-loop code. I do not recommend this approach, since it is a bad practice to make queries on the UI thread.
You have two options:
Change your foor loop to a function and call it after adding the values from the observer:
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
lookForMentor();
}
}
});
private void lookForMentor() {
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
Put the for inside the observer, change the Room DAO to return a List and use LiveData on your own viewmodel:
MentorViewModel.java:
MentorViewModel extends ViewModel {
private MutableLiveData<List<Mentor>> _mentorsLiveData = new MutableLiveData<List<Mentor>>();
public LiveData<List<Mentor>> mentorsLiveData = (LiveData) _mentorsLiveData;
void getAllMentors(){
//room db query
_mentorsLiveData.postValue(mentorsList);
}
}
EditActivity.java:
mentorsViewModel.getAllMentors();
mentorViewModel.mentorsLiveData.observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
mentorsListMentor.addAll(mentorList);
sp_CourseMentorAdapter.notifyDataSetChanged();
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
}
});
I'm creating a media player in JavaFX. In one of my methods, I've created a way to search for metadata in a Media-file and then display it in ImageView. Works fine first time, but as soon as I want to call it again using another Media object, the image doesn't show up. I'm a bit confused and inexperienced, but I think that perhaps I need to reset/stop the listener before going to next object in line?
So my question is! How do you remove the listener when "image" has been found, what do you type to make it happen?
If you think that there's another reason why my image wont display the second time, please let me know as well.
Thanks on purpose.
private void displayAlbumCover (){
// Will start to show a blank CD
File file = new File("src/sample/images/blank_cd.jpeg");
Image image = new Image(file.toURI().toString());
albumCoverView.setImage(image);
// However if an album cover is found in the meta-data it will be displayed
ObservableMap<String,Object> meta_data=me.getMetadata();
meta_data.addListener((MapChangeListener<String, Object>) ch -> {
if(ch.wasAdded()){
String key=ch.getKey();
Object value=ch.getValueAdded();
switch(key){
case "image":
albumCoverView.setImage((Image)value);
break;
}
}
});
}
ObservableMap has removeListner method. You can keep the listener instance to variable and then remove it later.
private MapChangeListener<String, Object> listener;
private void displayAlbumCover (){
// ...
this.listener = //...
meta_data.addListener(listener);
}
private void removeListener() {
me.getMetadata().removeListener(this.listener);
}
https://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableMap.html#removeListener-javafx.collections.MapChangeListener-
I'm trying to use a ListView as an Editor for Strings, that come out of a custom data model. I use TextFieldListCells with an appropriate StringConverter for the cells.
There is an add button next to the ListView that calls this method on action:
#FXML
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.getSelectionModel().select(wordListItems.indexOf(newItem));
wordListView.edit(wordListItems.indexOf(newItem));
wordListView.setEditable(false);
}
Where wordListView is the ListView and wordListItems is the ObservableList containing the data for the wordListView.
This does work, except for when the list is empty (not null), and I couldn't quite explain why, so I inspected the Java source code for help.
Here's what I found out so far: the edit(int) call on ListView changes the ListViews internal editIndex value, which is supposed to call the EDIT_START Event. The editIndex is an ReadOnlyIntegerWrapper in which I found some weird code that I can't quite understand and I'm not sure if thats actually producing a bug or I just can't see why they did it:
#Override
protected void fireValueChangedEvent() {
super.fireValueChangedEvent();
if (readOnlyProperty != null) {
readOnlyProperty.fireValueChangedEvent();
}
}
This method is called whenever the editIndex property of ListView is changed. The problem: readOnlyProperty is null, because it's not set anywhere. The only place I could find where it got set is in the getter:
public ReadOnlyIntegerProperty getReadOnlyProperty() {
if (readOnlyProperty == null) {
readOnlyProperty = new ReadOnlyPropertyImpl();
}
return readOnlyProperty;
}
(ReadOnlyIntegerImpl is an inner private class and readOnlyProperty is it's type)
Now to my actual question: Is this a bug or am I overseeing something? Is there a reason why I can't add and edit a newly created Element in my list like that when it's empty, or is it really just this getter not being called yet?
The source code you found just is code for lazy initializing the property.
Unless new value is assigned to the property or the property itself is requested, null can be used as the property to avoid unnecessary creation of property objects. This is not an issue here.
The issue seems to be the ListView cells not being updated before edit is called. This happens during layout, so "manually" calling layout before starting the edit should work:
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.layout();
wordListView.edit(wordListItems.size()-1);
wordListView.setEditable(false);
}
I found the article "Avoiding memory leaks", where it is said that the following code:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
is not a good idea, since:
When the screen orientation changes the system will, by default,
destroy the current activity and create a new one while preserving its
state. In doing so, Android will reload the application's UI from the
resources.
So the above code:
...leaks the first activity created upon the first screen orientation change. When a Drawable is attached to a view, the view is
set as a callback on the drawable. In the code snippet above, this
means the drawable has a reference to the TextView which itself has a
reference to the activity (the Context) which in turns has references
to pretty much anything (depending on your code.)
But, when screen orientation changes, the method setBackgroundDrawable(Drawable background) is called, which in turn calls:
background.setCallback(this);
The method Drawable.setCallback() is definied in the following way:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
So, now background should release the old reference to the previous TextView, and a new reference to the new TextView should be created.
So, it seems like changing screen orientation leaks a reference only until the activity is newly created.
Where am I going wrong ?
You are absolutely right. However, there is one subtle point: the article is from 2009. Back then, the implementation of setCallback was different:
Android <= 2.3.7:
public final void setCallback(Callback cb) {
mCallback = cb;
}
Android >= 4.0.1:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
Grepcode shows no source code of intermediate versions, this is the only diff I could quickly find.
So, again, you're absolutely right in this specific case (if you're targeting >14 that is). However, it is still very important to really think about what is actually happening when you keep a static reference to such items (like you did). There are numerous cases where you certainly could be leaking the Context.
I have a List<Presenter> presenterList;
With
public class Presenter(){
String name;
String imageRef; // Filename to be downloaded
Bitmap image;
(etc...)
}
I'm working with AsyncTask & once the image has downloaded, I wish to go through the list and set Image value to the newly download image.
so far i have
Presenter pres = PresenterList.get(Position);
pres.Image = new (Bitmap) downloadedImageFromImageRef;
however i fear that this will not relate to the Image value of the presenter within the list.
How do i refer, or even assign to the specific Presenter attribute within the list?
From working with C (many years ago), i belive somthing like a pointer to the value in which to assign .Image would work
Thank you in advace
Well, if you have C experience, then the thing to know about Java is that it doesn't use pointers, but it does use references. So if I'm understanding your problem correctly, you are already using the Image attribute of a Presenter instance elsewhere and then you want to fill it in later. Assigning pres.Image = new (Bitmap) DownloadedImageFromImageRef; would not work in this case because other objects are looking at a different Bitmap object reference.
What you might need to do is use an observer pattern -- it depends on the details of your problem. Here's an example:
Somewhere in the code I have a class Foo that wants to use the Image property from a Presenter instance. But, since that property isn't set until later, this class wants to be notified when it is ready (it is an observer).
public class Presenter {
String Name;
String ImageRef; // Filename to be downloaded
private Bitmap Image;
private PresenterImageObserver observer;
public void setImageObeserver(PresenterImageObserver pio) {
this.observer = pio;
}
public void setImage(Bitmap b) {
this.Image = b;
this.observer.imageLoaded(b);
}
}
public interface PresenterImageObserver {
public void imageLoaded(Bitmap b);
}
public class Foo implements PresenterImageObserver {
//Foo's constructor. It wants the image from presenter p, when it is ready
public Foo(Presenter p) {
p.setImageObserver(this);
}
public void imageLoaded(Bitmap b) {
//b contains the loaded image and this Foo instance can use it now!
}
}
You'd need to set the image using pres.setImage(new (Bitmap) downloadedImageFromImageRef);.
So you have to find in your list the Presenter for which the correct imageref. You have basically two options.
First, you simply iterate through your list
for (Presenter presenter: presenterList) {
if (presenter.imageref.equals(imageName) {
Presenter.image = new Bitmap(downloadedImage);
break; // found : stop iterations
}
}
Secondly, you can create a HashMap for your presenters, with the imageref as the key :
HashMap<String, Presenter> map = new HashMap<>();
for (Presenter presenter: presenterList) {
map.put(presenter.imageref, presenter);
}
Then, you can directly find the right presenter through map.get(imageName)