Google Maps: Different CustomInfowindow for each marker - android

I have an android application where a MapsActivity is included to display a number of markers using GoogleMaps.
The markers are created through Timestamp objects
Timestamp object attributes
(double lat,lon;
int stepSum;
long timeMilli;
String state=null;)
stored in Firebase Database.
So I retrieve each Timestamp from the database and try to create a marker with those attibutes above. My problem is that when I click a marker the custom info window is being displayed but its the same for all markers. It should show different attributes for different markers.
Why this is happening
In the drawMarkers() method I instantiate a separate infowindow when I am creating a new marker and set that info window to the
GoogleMap object with mMap.setInfoWindowAdapter(infoWindow);.
As I result mMap.setInfoWindowAdapter(infoWindow);is called as many times as markers are created and finally only the last infowindow survives. That is the problem but I can't figure out a solution.
How to implement an infowindow that when I click a marker it presents some kind of data and when I am clicking another marker it presents different kind of data (same layout, different attributes).
A valid example also would do.
CustomInfoWindow class
private class CustomInfoWindow implements GoogleMap.InfoWindowAdapter{
private String title=null,hour=null,state=null;
private int steps=0;
public CustomInfoWindow(){}
public CustomInfoWindow(String title, String hour, String state, int steps) {
this.title = title;
this.hour = hour;
this.state = state;
this.steps = steps;
}
#Override
public View getInfoWindow(Marker marker) {
return null;
}
public void setState(String state) {
this.state = state;
}
#Override
public View getInfoContents(Marker marker) {
LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
View root = inflater.inflate(R.layout.marker_infowindow,null);
TextView titleTextView = (TextView) root.findViewById(R.id.infowindow_title);
TextView hourTextView = (TextView) root.findViewById(R.id.infowindow_hour);
TextView stepsTextView = (TextView) root.findViewById(R.id.infowindow_steps);
TextView stateTextView = (TextView) root.findViewById(R.id.infowindow_state);
titleTextView.setText(title);
if (state!=null){
stateTextView.setText(getApplicationContext().getString(R.string.state)+" "+state);
stateTextView.setVisibility(View.VISIBLE);
}
hourTextView.setText(getApplicationContext().getString(R.string.time)+" "+hour);
stepsTextView.setText(getApplicationContext().getString(R.string.steps_so__far)+" "+steps);
return root;
}
public void setTitle(String title) {
this.title = title;
}
public void setHour(String hour) {
this.hour = hour;
}
public void setSteps(int steps) {
this.steps = steps;
}
}
How markers are drawn
private void drawMarkers(Calendar c){
String userId = this.user.getAccount().getId();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
final DatabaseReference timestampRef = FirebaseDatabase.getInstance().getReference()
.child(FirebaseConstant.TIMESTAMPS.toString());
timestampRef.child(userId).child(""+year).child(""+month).child(""+dayOfMonth)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
long childCounter=0;
for (DataSnapshot timestampSnapshot:dataSnapshot.getChildren()){
CustomInfoWindow infoWindow=new CustomInfoWindow();
Timestamp temp = timestampSnapshot.getValue(Timestamp.class);
if (temp!=null){
infoWindow.setTitle(getString(R.string.todays_timestamp));
infoWindow.setHour(getFormatedHourFromTimeMilli(temp.getTimeMilli()));
infoWindow.setSteps(temp.getStepSum());
if (temp.getState()!=null){
infoWindow.setState(temp.getState());
}
mMap.setInfoWindowAdapter(infoWindow);
drawTimestamp(temp);
infoWindow=null;
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
I have read google's tutorial about info windows but couldn't solve my problem.

Hope this example will help you just use it as simple xml layout.
Remember button will not work just textview will complete your requirement on the spot if you want get buttons in workable condition follow chose007 example.

My solution
Create a class that extends GoogleMap.InfoWindowAdapter. That will be your custom infowindow for the whole map.
Inside onMapReady() set an implementation of your customInfoWindow object to the GoogleMap object. That will be the one and only infowindow that it will be displayed when a user is clicking a marker from the map.
Finally, inside onMapReady() method set an OnMarkerClickListener to the GoogleMap object as well. Your implementation of the GoogleMap.OnMarkerClickListener.onMarkerClick(Marker marker) will only change the content (change the attributes of the infowindow object, call it however you want) of the infowindow, depending on which marker is clicked.

Related

How to change the appearance of an android view with a click listener and background worker?

I have an android app that users can use to check into a course at a certain time. Their daily schedule shows up on a RecyclerView as a series of CardViews. Each CardView has basic information about each class, including instructor name, course title, attendance status, etc..
The app communicates with a MySQL database through a php script which is called from an AsyncTask background worker. In the PostExecute method, the app receives a result from the php script ("Present" if checking in on time, "Tardy" if late, "Absent" if totally missed) to show in an AlertDialog. The PostExecute method also sets a String variable to equal the result.
The background worker is called from a click listener like so:
baf.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int bright = status.getResources().getColor(R.color.colorPresent);
status.setBackgroundColor(bright);
lol = getAdapterPosition()+1;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar c = Calendar.getInstance();
String time = sdf.format(c.getTime());
Log.d("Current Time",time);
String type="checkin";
PresentBackgroundWorker presentBackgroundWorker = new PresentBackgroundWorker(getActivity());
presentBackgroundWorker.execute(type,date,time, "t"+String.valueOf(lol));
String aaa = ontime;
status.setText(aaa);
}
});
The variable ontime (global variable) is the result from the php script, and its value is set from the background worker like so:
protected void onPostExecute(String result) {
writeitout(result);
alertDialog.setMessage(result);
alertDialog.show();
}
private void writeitout(String string) {
ontime = string;
Log.d("STATS",ontime);
}
Based on the Log.d() commands, the ontime variable is changing appropriately, but the change to the 'status' TextView is delayed by one click. That is, the result for the previous course, shows up for the present course. How do I make sure the changes in the ontime variable show up on time?
EDIT: ADDED ORIGINAL COURSEADAPTER
public class CourseAdapter extends RecyclerView.Adapter<CourseAdapter.MyViewHolder> {
private List<Course> courseList;
public CourseAdapter(List<Course> sc) { this.courseList = sc; }
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView title, teacher, type, status;
FloatingActionButton baf;
View subIten;
int lol;
public MyViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.texttitle);
teacher = (TextView) view.findViewById(R.id.textteach);
type = (TextView) view.findViewById(R.id.texttype);
baf = (FloatingActionButton) view.findViewById(R.id.fab);
status = view.findViewById(R.id.textstatus);
subIten = (View) view.findViewById(R.id.sub_item);
}
private void bind(Course course) {
title.setText(course.getTitle());
teacher.setText(course.getTeacher());
type.setText(course.getType());
baf.setImageResource(course.getPhotoID());
baf.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int bright = status.getResources().getColor(R.color.colorPresent);
status.setBackgroundColor(bright);
lol = getAdapterPosition()+1;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar c = Calendar.getInstance();
String time = sdf.format(c.getTime());
Log.d("Current Time",time);
String type="checkin";
PresentBackgroundWorker presentBackgroundWorker = new PresentBackgroundWorker(getActivity());
presentBackgroundWorker.execute(type,date,time, "t"+String.valueOf(lol));
}
});
}
}
#Override
public CourseAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview, parent, false);
MyViewHolder viewHolder = new MyViewHolder(itemView);
return viewHolder;
}
#Override
public void onBindViewHolder(final CourseAdapter.MyViewHolder holder, final int position) {
final Course course = courseList.get(position);
holder.bind(course);
}
public int getItemCount() {
return courseList.size();
}
}
The problem is you are saving the asynctask result in a variable and then later accessing it on button click.
So, what basically is happening here:
You click on a button
you start asynctask
your asynctask is still working. So, onTime still has no string. And, status.setText() is being called even before the asynctask produce result and save it in onTime.
your asynctask has finished it's job and saved the result in onTime, but the button has already finished it's rest of the code, so textview doesn't get the latest change.
you again click on the button
your asynctask again starts to work, but this time onTime has a value because previous asynctask saved it's result in it. so when status.setText() is being called, it set's the value of onTime (which holds the result from previous asynctask)
So, to fix the issue, you shouldn't update the textview on button click, rather update them inside asynctask's onPostExecute()
Just change your onPostExecute() like this and you have your solution.
protected void onPostExecute(String result) {
writeitout(result);
status.setText(result); //updating the textview directly here
alertDialog.setMessage(result);
alertDialog.show();
}
Also, remove this two lines from your baf clickListener
String aaa = ontime;
status.setText(aaa);

MaterialCalendarView Dotspan not appearing

I'm using this library: https://github.com/prolificinteractive/material-calendarview in my project. Here are segments of my code. I used the EventDecorator class that was provided in the documentation for the Dot Span and renamed it. The calendarView is a MaterialCalendarView in the EventFragment. I wanted to add a Dot Span whenever I click on a specific date in the calendarView, but it doesn't seem to be working/showing up. markedDates is an array list containing all the CalendarDay that were clicked on, so every time I click on a specific date in the calendarView, I add the CalendarDay to the array list. Does anyone know how to fix this/make it work?
public class CurrentDayDecorator implements DayViewDecorator {
private final int color;
private final HashSet<CalendarDay> dates;
public CurrentDayDecorator(int color, Collection<CalendarDay> dates) {
this.color = color;
this.dates = new HashSet<>(dates);
}
#Override
public boolean shouldDecorate(CalendarDay day) {
return dates.contains(day);
}
#Override
public void decorate(DayViewFacade view) {
view.addSpan(new DotSpan(5, color));
}
}
In EventFragment class
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
#Override
public void onDateSelected(#NonNull MaterialCalendarView widget, #NonNull CalendarDay date, boolean selected) {
markedDates.add(date);
currentDayDecorator = new CurrentDayDecorator(Color.WHITE, markedDates);
widget.addDecorator(currentDayDecorator);
widget.invalidateDecorators();
}
});
I know it's already too late but my solution can be beneficial for a lot more as I myself couldn't find a good implementation of adding dots. I will be showing a simple implementation of putting a red color dot on all those dates where user gonna click and I believe you can customise things accordingly. So, here we go.
First thing first, make a new java class "EventDecorator" and copy all the following code inside that-
public class EventDecorator extends AppCompatActivity implements DayViewDecorator {
private final int color = Color.parseColor("#FF0000");
private final CalendarDay dates;
public EventDecorator(CalendarDay dates) {
this.dates = dates;
}
#Override
public boolean shouldDecorate(CalendarDay day) {
return dates==day;
}
#Override
public void decorate(DayViewFacade view) {
view.addSpan(new DotSpan(5, color));
}
}
Now come to the fragment where we will be setting up the onDateChangegListener, So, copy all this code there inside the createView-
CalendarView calendarView = view.findViewById(R.id.calendarView); //remove view. if it's an activity
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
#Override
public void onDateSelected(#NonNull MaterialCalendarView widget, #NonNull CalendarDay date, boolean selected) {
EventDecorator eventDecorator= new EventDecorator(date);
widget.addDecorator(eventDecorator);
widget.invalidateDecorators();
}
});
So this is how things are working and if you want to use these dots only on those dates with some events then you can make an array of these dates, store them and then pass them to the EventDecorator class.
Hope this helps someone. Be free to ask anything.

Plot marker on map from autocomplete item clicked

I am able to display all items from Autocomplete suggestion but when i click an item i want the longitude and latitude coordinates associated with the item to be plotted on the map. I currently dont know how to achieve. `please help here.
I have pasted my method below
private void searchAutocomplete(){
DatabaseReference database = FirebaseDatabase.getInstance().getReference();
//Create a new ArrayAdapter with your context and the simple layout for the dropdown menu provided by Android
final ArrayAdapter<String> autoComplete = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1);
//Child the root before all the push() keys are found and add a ValueEventListener()
database.child("wr").child("clubs").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Basically, this says "For each DataSnapshot *Data* in dataSnapshot, do what's inside the method.
for (DataSnapshot suggestionSnapshot : dataSnapshot.getChildren()){
//Get the suggestion by childing the key of the string you want to get.
String suggestion = suggestionSnapshot.child("name1").getValue(String.class);
//Add the retrieved string to the list
autoComplete.add(suggestion);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
AutoCompleteTextView ACTV= (AutoCompleteTextView)findViewById(R.id.autocompleteView);
ACTV.setAdapter(autoComplete);
ACTV.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
});
}
You should update final ArrayAdapter<String> autoComplete to instead be something like final ArrayAdapter<Place> autoComplete and then implement something like:
public class Place {
private float latitude;
private float longitude;
private String name;
// implement your constructor
// implement your accessors
}
And then from your onClick you would received the Place, and you can just do something like place.getLatitude() and place.getLongitude.
That's how I would implement this.
You simply need to listen for click events and add a Marker when an item is clicked.
Example:
tv.setOnItemClickListener((adapterView, view, pos, id) -> {
mGoogleMap.addMarker(new MarkerOptions()
.position(mDataList.get(pos).getLocation()));
});
You'll need to make sure your google map has been loaded of course.

How to set info window details in Google Maps Clustering Utility Android?

I am trying to display a list of venues on Google Maps in Android, which can be clustered on zoom out and on zoom in unclustered.
WHEN UNCLUSTERED, an individual item info window can be opened to look at that venue details, and clicked to open a separate activity.
I am using this https://developers.google.com/maps/documentation/android-api/utility/marker-clustering?hl=en
I am doing this :
Getting Map Fragment in onResume()
#Override
public void onResume() {
super.onResume();
// Getting map for the map fragment
mapFragment = new SupportMapFragment();
mapFragment.getMapAsync(new VenuesInLocationOnMapReadyCallback(getContext()));
// Adding map fragment to the view using fragment transaction
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.venues_in_location_support_map_fragment_container, mapFragment);
fragmentTransaction.commit();
}
MapReadyCallback :
private class VenuesInLocationOnMapReadyCallback implements OnMapReadyCallback {
private static final float ZOOM_LEVEL = 10;
private final Context context;
public VenuesInLocationOnMapReadyCallback(Context context) {
this.context = context;
}
#Override
public void onMapReady(final GoogleMap map) {
// Setting up marker clusters
setUpClusterManager(getContext(), map);
// Allowing user to select My Location
map.setMyLocationEnabled(true);
// My location button handler to check the location setting enable
map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
#Override
public boolean onMyLocationButtonClick() {
promptForLocationSetting(getContext(), map);
// Returning false ensures camera try to move to user location
return false;
}
});
map.getUiSettings().setMyLocationButtonEnabled(true);
// Disabling map toolbar
map.getUiSettings().setMapToolbarEnabled(false);
}
}
Setting up Cluster Manager
private void setUpClusterManager(final Context context, GoogleMap map) {
// Declare a variable for the cluster manager.
ClusterManager<LocationMarker> mClusterManager;
// Position the map.
LatLng wocLatLng = new LatLng(28.467948, 77.080685);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(wocLatLng, VenuesInLocationOnMapReadyCallback.ZOOM_LEVEL));
// Initialize the manager with the context and the map.
mClusterManager = new ClusterManager<LocationMarker>(context, map);
// Point the map's listeners at the listeners implemented by the cluster
// manager.
map.setOnCameraChangeListener(mClusterManager);
map.setOnMarkerClickListener(mClusterManager);
// Add cluster items (markers) to the cluster manager.
addLocations(mClusterManager);
// Setting custom cluster marker manager for info window adapter
map.setInfoWindowAdapter(mClusterManager.getMarkerManager());
mClusterManager.getMarkerCollection().setOnInfoWindowAdapter(new MyLocationInfoWindowAdapter());
map.setOnInfoWindowClickListener(new MyMarkerInfoWindowClickListener());
}
Adding Cluster items (markers)
private void addLocations(ClusterManager<LocationMarker> mClusterManager) {
for (int i = 0; i < venuesDetailsJsonArray.length(); i++) {
try {
JSONObject thisVenueJson = (JSONObject) venuesDetailsJsonArray.get(i);
JSONObject thisVenueLocationJson = thisVenueJson.getJSONObject("location");
LocationMarker thisVenueMarker = new LocationMarker(thisVenueLocationJson.getDouble("latitude"),
thisVenueLocationJson.getDouble("longitude"), thisVenueJson.getInt("id"));
mClusterManager.addItem(thisVenueMarker);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
MyLocationInfoWIndowAdapter
private class MyLocationInfoWindowAdapter implements GoogleMap.InfoWindowAdapter {
#Override
public View getInfoWindow(Marker marker) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
Log.e("getInfoContent", marker.toString());
View venueInfoWindow = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.venues_map_item, null);
return venueInfoWindow;
}
}
MarkerInfoWindowClickListener
private class MyMarkerInfoWindowClickListener implements GoogleMap.OnInfoWindowClickListener {
#Override
public void onInfoWindowClick(Marker marker) {
// TODO: This is the click listener, that means all the info must be added as Tag to Marker
Intent venueDetailsDisplayIntent = new Intent(getActivity(), VenueDetailsDisplayActivity.class);
startActivity(venueDetailsDisplayIntent);
}
}
Location Marker class
public class LocationMarker implements ClusterItem{
private final LatLng mPosition;
private final int id;
public LocationMarker(double lat, double lng, int id) {
mPosition = new LatLng(lat, lng);
this.id = id;
}
#Override
public LatLng getPosition() {
return mPosition;
}
public int getId() {
return this.id;
}
}
The way that I am understanding the flow is this :
onResume --> fragmentTransaction --> VenuesInLocationOnMapReadyCallback --> setUpClusterManager --> addLocations (This adds Custom markers)
Marker Click --> MyLocationInfoWindowAdapter --> getInfoContents(Marker marker)
Marker Info Window click --> MyMarkerInfoWindowClickListener
According to my Understanding of process (I could be wrong):
I am adding an id to my custom LocationMarker when Adding markers in addLocations function.
I need to display different info in infoWindow for different markers.
InfoWindow is displayed using MyLocationInfoWindowAdapter-->getInfoContents(Marker marker)
But here is the rub, I can't find a way to figure out which marker has been clicked upon so that I can set appropriate info in InfoWindow.
On Click on opened InfoWindow I need to open a separate Activity. A/C to me InfoWindow click is handled using MyMarkerInfoWindowClickListener-->onInfoWindowClick(Marker marker) Here too I am having the same problem (I can't figure out which marker's info window has been clicked).

Is there any way to handle a button from a different activity?

Referring to the image below. I want to update the map when the user clicks the View Map button. However, since this is part of an expandable list view, it is in it's own class, while the map itself is in another fragment activity.
Is there any way to listen to the button click and update the map accordingly from another activity?
Thanks!
Here's what i have so far. I tried getting the parameter via
ExpandableListElement.polyLinePass
but it does not give me anything back so i can't update the map. Also another question, does the map update itself when values are changed like the polyLine, or do I need to setup the map again?
Map Display Activity:
public class OfflineViewer extends FragmentActivity {
private void setUpMap() {
List<LatLng> listTemp = util.decodePoly(ExpandableListElement.polyLinePass);
for(int l=0;l<listTemp.size() - 1;l++){
list.add(listTemp.get(l));
if(l==0) {
}
}
int listSize = list.size();
//get polyline from expandable list elements
for (int i = 0; i < list.size() - 1; i++) {
LatLng src = list.get(i);
LatLng dest = list.get(i + 1);
Polyline line = mMap.addPolyline(new PolylineOptions() //mMap is the Map Object
.add(new LatLng(src.latitude, src.longitude),
new LatLng(dest.latitude,dest.longitude))
.width(5).color(Color.BLUE).geodesic(true));
}
...
}
ExpandableListElement Activity:
public class ExpandableListElement extends RelativeLayout {
public ExpandableListElement(Context context, String routeName, String mode, String dist, String routeId, String start, String end, final String polyLine) {
super(context);
mContext = context;
setTextViewElements(routeName, mode, dist, routeId, start, end, polyLine);
Button randButton = new Button(mContext);
randButton.setText("View Map");
randButton.setId(mIdPool);
randButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
polyLinePass = polyLine;
}
});
...
}
You can handle that from your activity class. Lets say the map is in one fragment and the button in another on. They are both "hosted" by the same activity.
let your activity have a method like this and be sure it is public:
class MyActivity extends Activity {
private Fragment fragment1; //the frag with the button
private Fragment fragment2; //the frag with the map
public void updateMap(String argument) {
if(fragment2 != null)
fragment2.update(argument);
}
}
then from the fragment with the button (fragment1), call ((MyActivity) getActivity()).updateMap(argument) to update your Map:
class Fragment1 extends Fragment implements OnClickListener{
// this is called on button click
void onClick(...) {
((MyActivity) getActivity()).updateMap(argument)
}
}
Your second fragment needs a public method for upting the map:
class Fragment1 extends Fragment implements OnClickListener{
public void updateMap(String arguments) {
// update map...
}
}
This strategy can also be used in lists

Categories

Resources