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.
Related
Recyclerview is scrolling with it's LinearLayoutManager like that lm.scrollToPositionWithOffset(position, offset). How to get the view where in scrolled position before scrolling? The view that will scrolled returning null after scrolling because still not created. I've try Runnable, Observer and onLayoutCompleted but still null. How to get the view?
lm.scrollToPositionWithOffset(position, offset);
recyclerView.post(new Runnable(){
#Override
public void run(){
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = recyclerView.getLayoutManager().getChildAt(position); // returning null.
}
});
recyclerView.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = recyclerView.getLayoutManager().getChildAt(position); // returning null.
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}});
#Override // in LinearLayoutManager
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = this.getChildAt(position); // returning null.
}
View v = lm.getChildAt(position);
lm.scrollToPositionWithOffset(position, offset);
v.post(new Runnable() {
#Override
public void run() {
// View is ready here.
});
If you're trying to modify the view content, you should do this:
Modify your current model: itemList.get(position) // and change
any property here to handle that state
Call adapter.notifyItemChanged(position)
Make sure you have the right logic on your ViewHolder to handle
this changes and that should modify your View
But if you really wanna change things through the ViewHolder you can also do this: recyclerView.findViewHolderForAdapterPosition(position) and then:
if (null != holder) {
holder.itemView.findViewById(R.id.YOUR_ID) // call any method do you want to
}
I really recommend the first option, but that's up to you. Hope this helps you!
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 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);
}
}
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));
I have been searching for an answer to my problem, but I seem to get none, despite of how many tutorials I followed, how many questions I've gone through and how many things I've tried to do what I want. Basically, I stumbled upon some good tips, and still couldn't manage to do what wanted.
THE PROBLEM
I am creating an Android Application that will use Fragments (alongside with tabs). In these fragments, I have crucial information relating the application, such as text boxes, and buttons. However, I want to do something really simple, which is updating one of my fragments as I come back to it (imagine I swipe back to a fragment, and I update it with the relevant information). Where is the information stored? On a node.js server, to which I call every time I want information. So for that, I created the following structure.
THE STRUCTURE
First of all, I started off creating my Activity.
public class CentralActivity extends FragmentActivity {
CentralPagerAdapter mCentralActivity;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_central);
tabHandler();
}
public void tabHandler() {
mCentralActivity = new CentralPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.CentralPager);
mViewPager.setAdapter(mCentralActivity);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
//Action Bar Stuff
}
}
With this said, I need my CentralPagerAdapter, which I created as follows.
public class CentralPagerAdapter extends FragmentStatePagerAdapter {
private int nSwipes = 3;
public CentralPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new CentralFragment();
Bundle args = new Bundle();
args.putInt(CentralFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return nSwipes;
}
}
And now, my fragment, which is only a class that contains all of my views, and options and so on.
public class CentralFragment extends Fragment {
public static final String ARG_OBJECT = "object";
private View rootView;
private RESTFunction currentFunction;
//Has the info I want
private ArrayList<Integer> tickets = new ArrayList<Integer>();
#SuppressLint("HandlerLeak")
private Handler threadConnectionHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (currentFunction) {
case GET_CLIENT_TICKETS:
handleGetTickets(msg);
break;
case BUY_CLIENT_TICKETS:
break;
default:
break;
}
}
};
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
final Bundle args = getArguments();
handleFragments(inflater, container);
getTicketInfo(null);
return rootView;
}
private void handleFragments(LayoutInflater inflater, ViewGroup container) {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 1) {
rootView = inflater.inflate(R.layout.fragment_show_tickets,
container, false);
showTicketsHandler();
} else if (args.getInt(ARG_OBJECT) == 2) {
rootView = inflater.inflate(R.layout.fragment_buy_tickets,
container, false);
buyTicketsHandler();
} else {
rootView = inflater.inflate(R.layout.fragment_history_tickets,
container, false);
}
}
public void showTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
}
public void buyTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
//As well as button click listeners
}
public void getTicketInfo(ProgressDialog progDialog) {
//Connect to the thread to get the information
//In this case, I have no parameters
ConnectionThread dataThread = new ConnectionThread("myLink", Method.GET, null, threadConnectionHandler, progDialog);
dataThread.start();
}
//Get stuff from the resulting JSON and store it in the tickets ArrayList
private void handleGetTickets(Message msg) {
JSONObject ticketListing = (JSONObject) msg.obj;
try {
tickets.add(ticketListing.getInt("t1"));
tickets.add(ticketListing.getInt("t2"));
tickets.add(ticketListing.getInt("t3"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
And then, I have my thread..
public class ConnectionThread extends Thread {
private ConnectionRunnable runConnection;
private Handler mHandler;
private ProgressDialog progDialog;
public ConnectionThread(String link, Method method, ArrayList<NameValuePair> payload, Handler handler, ProgressDialog progDialog) {
runConnection = new ConnectionRunnable(link, method.toString(), payload);
mHandler = handler;
this.progDialog = progDialog;
}
#Override
public void run() {
runConnection.run();
threadMsg();
if(progDialog != null)
progDialog.dismiss();
}
public JSONObject getJSON() {
return runConnection.getResultObject();
}
private void threadMsg() {
Message msgObj = mHandler.obtainMessage();
msgObj.obj = getJSON();
mHandler.sendMessage(msgObj);
}
}
And ConnectionRunnable is where I run my HttpURLConnection.
SO WHAT DO I NEED?
Basically, what I'm trying to do, is to get the ticket information from the ConnectionThread BEFORE I load all my view and update them. Plus, I want to be able to swipe back and forth, and update my information on the array as I swipe through the screens (if I go to the second screen, the tickets will update, and if I come back to the first, they will re-update). So basically, call the ConnectionThread everytime I swipe around. If that is possible that, is.
WHAT HAVE I TRIED?
I've tried several things already, and all of them didn't actually help..
The usage of ProgressDialogs to stop the UI Thread on the onCreateView method of the fragment (no use, because it returns the rootView before it handles everything);
Making the UI Thread sleep for 1 second (I don't know why, it blocks all of them);
Overriding the instantiateMethod() of the Adapter, although I think I didn't do it correctly;
Overriding the saveState() of the Adapter, in order to prevent its saved states, and to then get new ticket information;
Giving the fragments tags to update their rootViews on the Adapter, but to no avail;
Getting the information in the activity, and everytime I make a purchase (second fragment), restart the whole activity to get the tickets, which I believe is a really, really bad solution.
I've read several articles, and I still couldn't find my answers.. It's really frustrating. Because it's something so simple, however, the fact that I have to run the HTTP calls on a different thread delays the whole UI updating process.
I've also read the AsyncTask's method. However, I feel like both Threads and AsyncTasks end up in the same.
WHAT TO DO NOW?
Well, that's what I was hoping to find. Because it ends up being annoying as it is.
POSSIBLE REASONS
Is it because I'm separating all classes into spread files, therefore making my work difficult?
Thank you for your time, guys, hope we can find a solution or something.
THE EDIT
So basically, after 4 hours of reading documents and tutorials, I figured that what I needed was setOffscreenPageLimit(int). However, it can't be set to 0, so I will have to do with a setOnPageChangeListener. Now, to figure how to refresh the fragment, and I'll be as good as new.
Alright, it works perfectly! Basically, I did this:
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
((CentralFragment)((CentralPagerAdapter) mViewPager.getAdapter()).instantiateItem(mViewPager, position)).refresh();
getActionBar().setSelectedNavigationItem(position);
}
});
Where my .refresh is:
public void refresh() {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 0) {
getTicketInfo(0);
} else if (args.getInt(ARG_OBJECT) == 1) {
getTicketInfo(1);
buyTicketsHandler();
} else {
//To Handle Later
}
}
It's as simple as refreshing the page before you go to it. Why didn't I remember this before..? So, here's the reference for those who ever need this!