item_place_picker.xml -- activity_explore.xml--activity_maps
There is a lot of similar questions, but I couldn't get answers. My problem is that when I am trying to use FusedLocationProviderClient to get users location, nothing show up on the recyclerView and logical says "RecyclerView: No adapter attached; skipping layout". I guess I am calling the adapter in wrong place/method so it can search for the adapter, which is declared in another class.
I know the problem is with the fusedLocationProviderClient and the onSucess method, because when I first built the app I used the old android framework location API, and everything worked perfectly. So the problem is in some way related to the on success method, but I can't figure out how.(The most important parts of the code are in the onConnected method).
public class ExploreActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
// The client object for connecting to the Google API
private FusedLocationProviderClient mFusedLocationClient;
List<FoursquareResults> frs;
// The TextView for displaying the current location
private TextView snapToPlace;
// The RecyclerView and associated objects for displaying the nearby coffee spots
private RecyclerView placePicker;
LinearLayoutManager placePickerManager;
private RecyclerView.Adapter placePickerAdapter;
// The base URL for the Foursquare API
String foursquareBaseURL = "https://api.foursquare.com/v2/";
// The client ID and client secret for authenticating with the Foursquare API
private String clientID;
private String clientSecret;
String categoryId = "4cce455aebf7b749d5e191f5";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_explore);
// The visible TextView and RecyclerView objects
snapToPlace = findViewById(R.id.snapToPlace);
placePicker = findViewById(R.id.fieldsList);
// Sets the dimensions, LayoutManager, and dividers for the RecyclerView
placePicker.setHasFixedSize(true);
placePickerManager = new LinearLayoutManager(this);
placePicker.setLayoutManager(placePickerManager);
placePicker.addItemDecoration(new DividerItemDecoration(placePicker.getContext(), placePickerManager.getOrientation()));
// Creates a connection to the Google API for location services
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
// Gets the stored Foursquare API client ID and client secret from XML
clientID = getResources().getString(R.string.foursquare_api_client_id);
clientSecret = getResources().getString(R.string.foursquare_client_secret);
}
@Override
public void onConnected(Bundle connectionHint) {
// Checks for location permissions at runtime (required for API >= 23)
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// Makes a Google API request for the user's last known location
mFusedLocationClient.getLastLocation()
.addOnSuccessListener(this, new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
// Logic to handle location object
// The user's current latitude, longitude, and location accuracy
String ll = location.getLatitude() + "," + location.getLongitude();
double llAcc = location.getAccuracy();
// Builds Retrofit and FoursquareService objects for calling the Foursquare API and parsing with GSON
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(foursquareBaseURL)
.addConverterFactory(GsonConverterFactory.create())
.build();
FoursquareService foursquare = retrofit.create(FoursquareService.class);
// Calls the Foursquare API to snap the user's location to a Foursquare venue
Call<FoursquareJSON> stpCall = foursquare.snapToPlace(
clientID,
clientSecret,
ll,
llAcc,
categoryId);
stpCall.enqueue(new Callback<FoursquareJSON>() {
@Override
public void onResponse(Call<FoursquareJSON> call, Response<FoursquareJSON> response) {
placePickerAdapter = new PlacePickerAdapter(getApplicationContext(), frs);
placePicker.setAdapter(placePickerAdapter);
// Gets the venue object from the JSON response
FoursquareJSON fjson = response.body();
FoursquareResponse fr = fjson.response;
List<FoursquareVenue> frs = fr.venues;
FoursquareVenue fv = frs.get(0);
// Notifies the user of their current location
snapToPlace.setText("You're at " + fv.name + ". Here's some fields nearby.");
}
@Override
public void onFailure(Call<FoursquareJSON> call, Throwable t) {
Toast.makeText(getApplicationContext(), "Can't connect to Foursquare's servers!", Toast.LENGTH_LONG).show();
finish();
}
});
// Calls the Foursquare API to explore nearby fields
Call<FoursquareJSON> fieldsCall = foursquare.searchCoffee(
clientID,
clientSecret,
ll,
llAcc,
categoryId);
fieldsCall.enqueue(new Callback<FoursquareJSON>() {
@Override
public void onResponse(Call<FoursquareJSON> call, Response<FoursquareJSON> response) {
// Gets the venue object from the JSON response
FoursquareJSON fjson = response.body();
FoursquareResponse fr = fjson.response;
FoursquareGroup fg = fr.group;
List<FoursquareResults> frs = fg.results;
// Displays the results in the RecyclerView
}
@Override
public void onFailure(Call<FoursquareJSON> call, Throwable t) {
Toast.makeText(getApplicationContext(), "Can't connect to Foursquare's servers!", Toast.LENGTH_LONG).show();
finish();
}
});
} else {
Toast.makeText(getApplicationContext(), "Can't determine your current location!", Toast.LENGTH_LONG).show();
finish();
}
}
});
}
}
@Override
protected void onResume() {
super.onResume();
mFusedLocationClient.getLastLocation();
}
@Override
protected void onPause() {
super.onPause();
mFusedLocationClient.flushLocations();
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Toast.makeText(getApplicationContext(), "Mr. Jitters can't connect to Google's servers!", Toast.LENGTH_LONG).show();
finish();
}
}
Adapter code:
//PlacePickerAdapter represents the adapter for attaching venue data to the RecyclerView within
//PlacePickerActivity. This adapter will handle a list of incoming FoursquareResults and parse them
// into the view.
public class PlacePickerAdapter extends RecyclerView.Adapter<PlacePickerAdapter.ViewHolder> {
// The application context for getting resources
private Context context;
// The list of results from the Foursquare API
private List<FoursquareResults> results;
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// The venue fields to display
TextView name;
TextView address;
TextView rating;
TextView distance;
String id;
double latitude;
double longitude;
public ViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Gets the appropriate view for each venue detail
name = (TextView)v.findViewById(R.id.placePickerItemName);
address = (TextView)v.findViewById(R.id.placePickerItemAddress);
rating = (TextView)v.findViewById(R.id.placePickerItemRating);
distance = (TextView)v.findViewById(R.id.placePickerItemDistance);
}
@Override
public void onClick(View v) {
// Creates an intent to direct the user to a map view
Context context = name.getContext();
Intent i = new Intent(context, DetailFieldActivity.class);
// Passes the crucial venue details onto the map view
i.putExtra("name", name.getText());
i.putExtra("ID", id);
i.putExtra("latitude", latitude);
i.putExtra("longitude", longitude);
// Transitions to the map view.
context.startActivity(i);
}
}
public PlacePickerAdapter(Context context, List<FoursquareResults> results) {
this.context = context;
this.results = results;
}
@Override
public PlacePickerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_place_picker, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Sets the proper rating colour, referenced from the Foursquare Brand Guide
double ratingRaw = results.get(position).venue.rating;
if (ratingRaw >= 9.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQKale));
} else if (ratingRaw >= 8.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQGuacamole));
} else if (ratingRaw >= 7.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQLime));
} else if (ratingRaw >= 6.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQBanana));
} else if (ratingRaw >= 5.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQOrange));
} else if (ratingRaw >= 4.0) {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQMacCheese));
} else {
holder.rating.setBackgroundColor(ContextCompat.getColor(context, R.color.FSQStrawberry));
}
// Sets each view with the appropriate venue details
holder.name.setText(results.get(position).venue.name);
holder.address.setText(results.get(position).venue.location.address);
holder.rating.setText(Double.toString(ratingRaw));
holder.distance.setText(Integer.toString(results.get(position).venue.location.distance) + "m");
// Stores additional venue details for the map view
holder.id = results.get(position).venue.id;
holder.latitude = results.get(position).venue.location.lat;
holder.longitude = results.get(position).venue.location.lng;
}
@Override
public int getItemCount() {
return results.size();
}
DetailActivityCode, which gets called when item from recyclerView item is clicked:
public class DetailFieldActivity extends FragmentActivity
implements OnMapReadyCallback, GoogleMap.OnInfoWindowClickListener {
// The Google Maps object.
private GoogleMap mMap;
// The details of the venue that is being displayed.
private String venueID;
private String venueName;
private double venueLatitude;
private double venueLongitude;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
getActionBar().setHomeButtonEnabled(true);
getActionBar().setDisplayHomeAsUpEnabled(true);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
// Retrieves venues details from the intent sent from PlacePickerActivity
Bundle venue = getIntent().getExtras();
venueID = venue.getString("ID");
venueName = venue.getString("name");
venueLatitude = venue.getDouble("latitude");
venueLongitude = venue.getDouble("longitude");
setTitle(venueName);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
// Centers and zooms the map into the selected venue
LatLng venue = new LatLng(venueLatitude, venueLongitude);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(venue, 16));
// Creates and displays marker and info window for the venue
Marker marker = mMap.addMarker(new MarkerOptions()
.position(venue)
.title(venueName)
.snippet("View on Foursquare"));
marker.showInfoWindow();
mMap.setOnInfoWindowClickListener(this);
// Checks for location permissions at runtime (required for API >= 23)
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// Shows the user's current location
mMap.setMyLocationEnabled(true);
}
}
@Override
public void onInfoWindowClick(Marker marker) {
// Opens the Foursquare venue page when a user clicks on the info window of the venue
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://foursquare.com/v/" + venueID));
startActivity(browserIntent);
}
}
FoursquareGroup:
//FoursquareGroup describes a group object from the Foursquare API.
public class FoursquareGroup {
// A results list within the group.
List<FoursquareResults> results = new ArrayList<FoursquareResults>();
}
foursquareJSON:
//FoursquareJSON describes a JSON response from the Foursquare API.
public class FoursquareJSON {
// A response object within the JSON.
FoursquareResponse response;
}
FoursquareLocation:
//FoursquareLocation describes a location object from the Foursquare API.
public class FoursquareLocation {
// The address of the location.
String address;
// The latitude of the location.
double lat;
// The longitude of the location.
double lng;
// The distance of the location, calculated from the specified location.
int distance;
}
FoursquareResponse:
//FoursquareResponse describes a response object from the Foursquare API.
public class FoursquareResponse {
// A group object within the response.
FoursquareGroup group;
List<FoursquareVenue> venues = new ArrayList<>();
}
FoursquareResults:
//FoursquareResults describes a results object from the Foursquare API.
public class FoursquareResults {
// A venue object within the results.
FoursquareVenue venue;
}
FoursquareService:
//FoursquareService provides a Retrofit interface for the Foursquare API.
public interface FoursquareService {
// A request to snap the current user to a place via the Foursquare API.
@GET("venues/search?v=20161101&limit=1") //search endpoint
Call<FoursquareJSON> snapToPlace(@Query("client_id") String clientID,
@Query("client_secret") String clientSecret,
@Query("ll") String ll,
@Query("llAcc") double llAcc,
@Query("categoryId") String categoryId);
// A request to search for nearby coffee shop recommendations via the Foursquare API.
@GET("search/recommendations?v=20161101&intent=soccer")
Call<FoursquareJSON> searchCoffee(@Query("client_id") String clientID,
@Query("client_secret") String clientSecret,
@Query("ll") String ll,
@Query("llAcc") double llAcc,
@Query("categoryId") String categoryId);
}
FoursquareVenue:
public class FoursquareVenue {
// The ID of the venue.
String id;
// The name of the venue.
String name;
// The rating of the venue, if available.
double rating;
// A location object within the venue.
FoursquareLocation location;
}