I'm trying to display a few points (points of interest) with custom InfoWindow in google maps android. My problem is that I cannot put different information in the different points. I'm facing problems updating the textview content of the popup layout. See my sample code below.
My infoWindowAdapter code:
public class poisInfoWindowAdapter implements InfoWindowAdapter {
#Override
public View getInfoWindow(Marker arg) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
//Get Layout of POI's popups and assign values to text views.
View InfoPopupLayout = getLayoutInflater().inflate(R.layout.infopopup, null);
TextView t = ((TextView)InfoPopupLayout.findViewById(R.id.title));
t.setText(name);
return InfoPopupLayout;
}
}
The code responsable for adding Points to map:
public void onPostExecute(String responsePois) {
try {
JSONArray P = new JSONArray(responsePois);
for (int i = 0; i < P.length(); i++) {
JSONObject pois = P.getJSONObject(i);
position = new LatLng(pois.getDouble("y"), pois.getDouble("x"));
name = pois.getString("name_pt");
Map.setInfoWindowAdapter(new poisInfoWindowAdapter());
Map.addMarker(new MarkerOptions()
.position(position)
.title(name)
);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
In this case all of my points get the same name. The infoWindowAdapter class can't get the correct values from name variable. Anyone knows how to resolve this problem?
I think that what I say and how I explain is understandable but if not, tell me and I will answer.
Thanks
First of all put your Map.setInfoWindowAdapter(new poisInfoWindowAdapter()); outside your for loop in onPostExecute(....)
and second implement your poisInfoWindowAdapter() like below:
public class poisInfoWindowAdapter implements InfoWindowAdapter {
#Override
public View getInfoWindow(Marker arg) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
//Get Layout of POI's popups and assign values to text views.
View InfoPopupLayout = getLayoutInflater().inflate(R.layout.infopopup, null);
TextView t = (TextView)InfoPopupLayout.findViewById(R.id.title);
t.setText(marker.getTitle());
TextView t2 = (TextView)InfoPopupLayout.findViewById(R.id.title2);
t2.setText(marker.getSnippet());
return InfoPopupLayout;
}
}
Update: set Marker as
Currnt = mMap.addMarker(new MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.drawable.mark_red))
.position(new LatLng(latitude,longitude)
.title(locations)
.snippet(city));
Related
I have a fragment which displays a popup when the user is successfully logged in. If I navigate to a new fragment and come back, the popup with the previous username is shown again. I fixed this problem using SingleLiveEvent, but I now have to refactor my code to use MediatorLiveData as my data can come from 2 sources (remote and database), and it is not compatible with SingleLiveEvent.
I tried using an event wrapper and removing observers on onDestroyView() but so far nothing is working, the livedata onChanged function keeps getting called when I move back to the fragment. Here is some of my fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentDashboardBinding.inflate(inflater, container, false);
binding.setLifecycleOwner(getActivity());
//Get the attendanceViewModel for registering attendance
attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
clokedInOutMember = attendanceAndMember.member;
}
showResultClockInOutPopup();
});
return binding.getRoot();
}
private void showResultClockInOutPopup() {
clockInBuilder = new AlertDialog.Builder(getActivity());
View view = getLayoutInflater().inflate(R.layout.status_clock_in_out_popup, null);
TextView responseClockInOut = view.findViewById(R.id.responseClockInOut);
Button dismissButton = view.findViewById(R.id.dismissButton);
//Setup Popup Text
if (clokedInOutMember != null) {
if (StringToBool(clokedInOutMember.is_clocked_in_temp)) {
responseClockInOut.setText("Bienvenue " + clokedInOutMember.name + ", tu es bien enregistré(e).");
} else {
responseClockInOut.setText("Désolé de te voir partir " + clokedInOutMember.name + ", à bientôt!");
}
} else {
responseClockInOut.setText("Oups, il semblerait qu'il y ait une erreur...\n Essaye à nouveau.");
}
[..SETUP ALERTDIALOG...]
//Dismiss popup
dismissButton.setOnClickListener(v -> {
clockInResultDialog.dismiss();
clockInResultPopupShowed = false;
clokedInOutMember = null;
});
clockInResultDialog.show();
clockInResultPopupShowed = true;
}
}
#Override
public void onDestroyView() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onDestroyView();
}
And here is my ViewModel, I have to use transformations as I am getting the userId from the fragment, passing to the Viewmodel which passes it to the repository for query (maybe there is a better way?):
public class AttendanceViewModel extends AndroidViewModel {
private AttendanceRepository repository = AttendanceRepository.getInstance();
public LiveData<AttendanceMemberModel> mAttendanceAndMember;
private MutableLiveData<String> mId = new MutableLiveData<>();
private MediatorLiveData<AttendanceMemberModel> mObservableAttendance = new MediatorLiveData<AttendanceMemberModel>();
{
mObservableAttendance.setValue(null);
mAttendanceAndMember = Transformations.switchMap(mId, id -> {
return repository.saveAttendance(id);
});
mObservableAttendance.addSource(mAttendanceAndMember, mObservableAttendance::setValue);
}
public AttendanceViewModel(#NonNull Application application) {
super(application);
}
public LiveData<AttendanceMemberModel> getAttendance() {
return mObservableAttendance;
}
public void setMemberId(String id) {
mId.setValue(id);
}
#Override
protected void onCleared() {
mObservableAttendance.setValue(null);
super.onCleared();
}
}
I can suggest you two ways. First create a boolean variable whether dialog is shown in Fragment and after showing dialog set it to true and before showing dialog check if dialog is shown. Second way is after showing dialog set livedata value to null and check if observer value null before showing dialog. I prefer second way.
Use anyone of them, which works and behaves according to your need.
#Override
public void onPause() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onPause();
}
#Override
public void onStop() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onStop();
}
Fragment Life Cycle
Have a look at the lifecycle of the fragment, it will give you bit more idea.
Let me know if this works or not.
The best way to the same is to bind your view model in OnViewCreated meathod.
#Override
public void onActivityCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
setUpObservers();
}
private void setUpObservers() {
attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
clokedInOutMember = attendanceAndMember.member;
}
showResultClockInOutPopup();
});
}
If still it don't work kindly let me know. Thank you.
I want to display a list of match objects (match = two users having liked each other) in a recycler view with the help of an adapter.
This is my activity which is meant to display those matches:
public class MatchesActivity extends AppCompatActivity {
// variables:
private RecyclerView mMatchesRecyclerView;
private RecyclerView.Adapter mMatchItemAdapter;
private RecyclerView.LayoutManager mMatchesLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_matches);
mMatchesRecyclerView = findViewById(R.id.matches_recyclerView);
mMatchesRecyclerView.setNestedScrollingEnabled(false);
mMatchesRecyclerView.setHasFixedSize(true);
// set layout manager & pass it to the recycler view:
mMatchesLayoutManager = new LinearLayoutManager(MatchesActivity.this);
mMatchesRecyclerView.setLayoutManager(mMatchesLayoutManager);
// set match adapter & pass it to the recycler view:
mMatchItemAdapter = new MatchItemAdapter(getMatchesList(), MatchesActivity.this);
mMatchesRecyclerView.setAdapter(mMatchItemAdapter);
// add test items to the recycler view:
Match testMatch = new Match("abcdefgh");
matchesList.add(testMatch);
mMatchItemAdapter.notifyDataSetChanged();
Log.d("MatchesActivity", "TEST LIST: " + matchesList.toString());
}
private ArrayList<Match> matchesList = new ArrayList<Match>();
private List<Match> getMatchesList() {
Log.d("MatchesActivity", "getMatchesList function: " + matchesList.toString());
return matchesList;
}
}
And this is my adapter which is supposed to inflate the relevant layout & populate it with relevant object data:
public class MatchItemAdapter extends RecyclerView.Adapter<MatchViewholder> {
private List<Match> mMatchesList;
private Context mViewContext;
public MatchItemAdapter(List<Match> matchesList, Context context) {
this.mMatchesList = matchesList;
this.mViewContext = context;
Log.d("MatchItemAdapter", "Constructor: " + mMatchesList.toString());
}
// inflate the layout:
#Override
public MatchViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_matches, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutView.setLayoutParams(lp);
MatchViewholder matchViewholder = new MatchViewholder(layoutView);
Log.d("MatchItemAdapter", "onCreateViewHolder: " + mMatchesList.toString());
return matchViewholder;
}
// populate each row within the layout:
#Override
public void onBindViewHolder(MatchViewholder holder, int position) {
Log.d("MatchItemAdapter", "onBindViewHolder: " + mMatchesList.toString());
holder.mMatchID.setText(mMatchesList.get(position).getMatchID());
}
#Override
public int getItemCount() {
return 0;
}
}
The Match class currently only takes matchID parameter which is string. An object is created with a default image and this matchID string.
At the moment, I have no real match objects from database ready, so I wanted to check that the recycler view along with adapter are working as expected before i move on to that later.
However, when I go to Matches Activity, it is empty, showing nothing at all. As you can see from the MatchesActivity onCreate method, I created a test Match object with matchID = "abcdefgh" and then added that to the matchesList. So I am expecting the "abcdefgh" text to be passed to the adapter and to be shown in the MatchesActivity.
My log statements indicate that the Match object has been created and added to the list successfully, however, getMatchesList() function returns an empty list which is then used in the Adapter constructor too, (I think this is) causing Activity not show anything.
I am relatively new to Android and Java development, especially recycler view and adapters, but from what I gathered it seems to be as if the
mMatchItemAdapter.notifyDataSetChanged();
is not working properly as everything seems to be fine up until that point. Any help would be appreciated!
You're returning 0. What you should do instead is return the length of the mMatchesList list.
#Override
public int getItemCount() {
return mMatchesList.size();
}
I have a couple of markers on my map. To each one of them, I want to inflate a custom infoWindow.
The problem i'm having is that the infoWindow is the same for each one. I have read a couple of stack threads but I haven't figured out how to fix it.
Snippet where I add the markers to the map
for (int i = 0; i<cityObjects.size(); i++){
CityObject cObject = cityObjects.get(i);
Coordinates loc = cObject.getCoordinates();
LatLng pos = new LatLng(loc.getLatitude(), loc.getLongitude());
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName()));
loadInfoWindow(cObject.getImgs().get(0), cObject.getName());
builder.include(pos);
}
Method to inflate the custom infoWindow
public void loadInfoWindow(final String url, final CharSequence title) {
mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker arg0) {
arg0.getId();
View v = getActivity().getLayoutInflater().inflate(R.layout.layout_info_window, null);
Button info = (Button) v.findViewById(R.id.infoButton);
info.setText(title);
BitmapLayout back = (BitmapLayout) v.findViewById(R.id.bitmapBackground);
Picasso.with(getContext()).load(url).into(back);
return v;
}
#Override
public View getInfoContents(Marker arg0) {
return null;
}
});
}
I read something about setInfoWindowAdapter being a setter and therefore overrides the infoWindow each time the for-loop iterates. Does anyone have a good solution on how to recognize the markers so I can inflate different layouts?
You can use marker.setTag() and marker.getTag(). You can give a tag to marker.for example
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName())).setTag(1);
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName())).setTag(2);
and then in loadInfoWindow method
if((int)arg0.getTag==1)
{
//do something
}
else{
//do something
}
I feel a bit stupid for not seeing this but I managed to write a solution.
Thanks to #chetanprajapat for getting me on the right track.
Since I have all information about the marker(location, title), I can just pass that into a created class which will check for the title and then inflate the proper layout.
Created class for inflating the correct layout
public class CustomInfoWindowAdapter implements GoogleMap.InfoWindowAdapter{
#Override
public View getInfoWindow(Marker arg0) {
for (CityObject cityObject : cityObjects){
if (arg0.getTitle().equals(cityObject.getName())){
View v = getActivity().getLayoutInflater().inflate(R.layout.layout_info_window, null);
Button info = (Button) v.findViewById(R.id.infoButton);
info.setText(cityObject.getName());
BitmapLayout back = (BitmapLayout) v.findViewById(R.id.bitmapBackground);
Picasso.with(getContext()).load(cityObject.getImgs().get(0)).into(back);
return v;
}
}
return null;
}
#Override
public View getInfoContents(Marker arg0) {
return null;
}
}
And then I just set the adapter to a new Instance of CustomInfoWindowAdapter
mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
Again, thanks to #chetanprajapat for the help.
I am making use of recycler view. I have a layout that is highlighted in light red,this layout is included for each item in the recycler view. The light red layout is placed over the background image. I am using setTag method to identify the clicks of the buttons in red layout. That is working properly when i click i get the position. The problem is i want to change the image at specific position.
For example : Consider the heart button. I have set a tag on it like this.
heartButton = findViewById(id);
heartButton.setTag(position);
now i get the position by using the getTag method. But now i want to change the image of the heartButton at the a specific position. Is there something like
heartButton.getTag(position).setImageResouce(drawable);
If not how do i do this then.
use setBackgroundResource(R.drawable.XXX)
http://developer.android.com/reference/android/view/View.html#setBackgroundResource(int)
Proper way to do this is,
You have to keep the state of the heart button stored in the model(POJO) which is passed to custom adapter.
e.g.
class ModelListItem{
public static final int HEART=1,BROKEN_HEART=2;
int heartButtonState;
}
Now in onClick() of heart button, get that object from adapter using position,cosidering you have already figured it out on how to preserve position from heart button
ModelListItem item = (ModelListItem)adapter.getItem(position)
Change the state of heart button;
item.setHeartButtonState(ModelListItem.BROKEN_HEART);
adapter.notifyDatasetChanged();
You already know below explaination but just in case
To work this properly,in your getView methode of adapter you need to put the check on heartButtonState(); and use appropriate image resource.
getView(BOILERPLATE){
BOILERPLATE
switch(item.getheartButtonState()){
case ModelItemList.HEART:
heartbutton.setImageResource(heart_image);
break;
case ModelItemList.BROKEN_HEART:
heartbutton.setImageResource(broken_heart_image);
break;
}
I made a custom click listener and updated the like in the setter getter.But this works only when the view has been moved out of the view (i think it is the scrapeview)
The Setter Getter Class
public class DemoData {
int background;
boolean liked;
public DemoData(int background) {
this.background = background;
}
public int getBackground() {
return background;
}
// public void setBackground(int background) {
// this.background = background;
// }
public boolean isLiked() {
return liked;
}
public void setLiked(boolean liked) {
this.liked = liked;
}
}
The onBindViewHolder function of the recycler view
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
background = (ImageView) holder.view.findViewById(R.id.image);
layout = (LinearLayout) holder.view.findViewById(R.id.layout);
delete = (ImageView) layout.findViewById(R.id.delete);
lock = (ImageView) layout.findViewById(R.id.lock);
delete.setTag("delete_"+position);
lock.setTag("lock_"+position);
if(Constants.demoDatas.get(position).isLiked()){
delete.setImageResource(R.drawable.ic_launcher);
}
else{
delete.setImageResource(android.R.drawable.ic_delete);
}
delete.setOnClickListener(new CustomClickListener(position));
lock.setOnClickListener(new CustomClickListener(position));
}
The custom click listener is as below
public class CustomClickListener implements View.OnClickListener {
int position;
public CustomClickListener(int position) {
this.position = position;
}
#Override
public void onClick(View v) {
String tag = (String) v.getTag();
String identifier[] = tag.split("_");
// this line saves my state in the Setter Getter Class
Constants.demoDatas.get(position).setLiked(true);
}
}
This question already has an answer here:
How can I get Resources by name (string) and not by integer
(1 answer)
Closed 9 years ago.
Ok, here is what I have:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
title = (TextView) findViewById(R.id.title);
description = (TextView) findViewById(R.id.description);
Spinner dropdown = (Spinner) findViewById(R.id.mainMenu);
final String options[] = {"-Turf Diseases-", "Dollar Spot", "Red Thread"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,options);
dropdown.setAdapter(adapter);
dropdown.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
newSelection(options[position]);
}
public void onNothingSelected(AdapterView<?> arg0) {}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public void newSelection(String selection) {
if(!selection.contains("-")) {
title.setText(selection);
selection=selection.replace(" ", "_");
selection=selection.toUpperCase();
description.setText("#string/DESC_"+selection);
}
}
The string array of options[] holds titles of strains of lawn diseases (the purpose of the app). It is in a spinner list in the main activity, and when a user clicks a title the action listener calls this last method, newSelection. This method is supposed to format the title to: WORD_WORD.
I have the descriptions saved as predefined strings in strings.xml, all starting with DESC_WORD_WORD. So by my logic, I could do this: description.setText("#string/DESC_"+selection); and it would easily locate the corresponding description in strings.xml.
This is, in fact, what has not ended up happening. The text just changes to the "#string/DESC_WORD_WORD" instead of the predefined string. I'm trying to think like an object-oriented programmer, but it isn't working out for me... I am fairly new to android, so go easy on me if this is a dumb question.
You need to get your resource by its string ID which is done thusly...
#SuppressWarnings("rawtypes")
public static int getResourceId(String name, Class resType){
try {
Class res = null;
if(resType == R.drawable.class)
res = R.drawable.class;
if(resType == R.id.class)
res = R.id.class;
if(resType == R.string.class)
res = R.string.class;
Field field = res.getField(name);
int retId = field.getInt(null);
return retId;
}
catch (Exception e) {
// Log.d(TAG, "Failure to get drawable id.", e);
}
return 0;
}
This is an example of a static method that will take a String that is your resource ID and the type of resource it is so you'd call it..
myText.setText( getResourceId("DESC_WORD_WORD", R.strings.class));