ListView に結果を表示する Firebase GeoFire クエリを作成しています。この ListView の例はemanueletから入手しました。私は、emanuelet によって共有された要点で動作するようになりました。これで、geoQuery の logcat の値も取得できます。
D/GeoPostsFragment: Key -KMsopZitLFzcxFuPOCn entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: Key -KMu2IcjIyMx5Qpb5Kfh entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: Key -KMz3knxKsAAixlf4ijY entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: Key -KMzXh2Kf7nLdOPqnMpY entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: Key -KN1Z1qZZZzksPlmwG75 entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: Key -KN6ltFbtLgpR5GbPki6 entered the search area at [40.7853889,-122.4056973]
D/GeoPostsFragment: All initial data has been loaded and events have been fired!
これは、firebase で GeoFire データを構造化した方法です。
これが GeoQuery のフラグメントです。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(LOG_TAG, "onCreate");
super.onCreate(savedInstanceState);
mContext = getActivity();
settings = PreferenceManager.getDefaultSharedPreferences(mContext);
setHasOptionsMenu(true);
mFirebaseRef = FirebaseDatabase.getInstance().getReference().child("posts");
//I initialize the object that will be used to retrieve the location
mGeoRef = FirebaseDatabase.getInstance().getReference("GeoFire");
geoFire = new GeoFire(mGeoRef);
mItemListAdapter = new ItemListAdapter(mFirebaseRef.equalTo("posts"), post, R.layout.include_post_text, getActivity());
}
@Override
public void onStart() {
super.onStart();
center = new GeoLocation(40.7853889, -122.4056973);
if (center.latitude != 0 && center.longitude != 0 && !mActiveGeoQuery) {
startGeoQuery();
} else if (mActiveGeoQuery) {
Log.d(LOG_TAG, "geoquery already active");
} else {
Log.d(LOG_TAG, "center not setted");
//I first try to set the center at the Last Known Location if retrieved
if (Double.isNaN(mLatitudeText) && mLatitudeText != 0) {
Log.d(LOG_TAG, "center setted from fused location");
center = new GeoLocation(40.7853889, -122.4056973);
startGeoQuery();
}
}
checkForItems();
}
@Override
public void onResume() {
super.onResume();
mItemListAdapter.notifyDataSetChanged();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.geo_fragment, container, false);
ButterKnife.bind(this, rootView);
mListView = (ListView) mListContainer.findViewById(R.id.list);
mListContainer.setViewState(MultiStateView.VIEW_STATE_LOADING);
mListContainer.setViewState(MultiStateView.VIEW_STATE_EMPTY);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Post cursor = (Post) mListView.getItemAtPosition(i);
if (cursor != null) {
mCallbacks.onItemSelected(cursor, view);
}
mPosition = i;
}
});
return rootView;
}
private void bindListView() {
mListView.setAdapter(mItemListAdapter);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
private void startGeoQuery() {
// safety net in case that for some reasons the returned radius is 0
query = geoFire.queryAtLocation(new GeoLocation(40.7853889, -122.4056973), 1);
Log.d(LOG_TAG, "center: " + center.toString() + ", radius: " + 0.2);
query.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
Log.d(LOG_TAG, "Key " + key + " entered the search area at [" + location.latitude + "," + location.longitude + "]");
DatabaseReference tempRef = mFirebaseRef.child("posts")
.child(key);
tempRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
// I add the deal only if it doesn't exist already in the adapter
String key = snapshot.getKey();
if (!mItemListAdapter.exists(key)) {
Log.d(LOG_TAG, "item added " + key);
mItemListAdapter.addSingle(snapshot);
Log.d(LOG_TAG,"Snapshot added " + snapshot);
mItemListAdapter.notifyDataSetChanged();
mListContainer.setViewState(MultiStateView.VIEW_STATE_CONTENT);
} else {
//...otherwise I will update the record
Log.d(LOG_TAG, "item updated: " + key);
mItemListAdapter.update(snapshot, key);
mItemListAdapter.notifyDataSetChanged();
}
}
@Override
public void onCancelled(DatabaseError firebaseError) {
Log.d(LOG_TAG, "cancelled with error:" + firebaseError.getMessage());
}
});
}
@Override
public void onKeyExited(String key) {
Log.d(LOG_TAG, "deal " + key + " is no longer in the search area");
mItemListAdapter.remove(key);
isListEmpty();
}
@Override
public void onKeyMoved(String key, GeoLocation location) {
Log.d(LOG_TAG, String.format("Key " + key + " moved within the search area to [%f,%f]", location.latitude, location.longitude));
}
@Override
public void onGeoQueryReady() {
Log.d(LOG_TAG, "All initial data has been loaded and events have been fired!");
isListEmpty();
mActiveGeoQuery = true;
}
@Override
public void onGeoQueryError(DatabaseError error) {
Log.e(LOG_TAG, "There was an error with this query: " + error);
mListContainer.setViewState(MultiStateView.VIEW_STATE_ERROR);
mListContainer.setContentDescription("GeoQueryError");
mActiveGeoQuery = false;
}
});
bindListView();
}
これは、カスタム FirebaseListAdapter を拡張する、私が使用している ItemListAdapter です。
@BindView(R.id.post_body)
TextView exampleView;
Context context;
public ItemListAdapter(Query ref,Class<Post> postClass, int layout, Activity activity) {
super(ref,postClass,layout,activity);
this.context = activity.getApplicationContext();
}
/**
* Bind an instance of the ExampleObject class to our view. This method is called by <code>FirebaseListAdapter</code>
* when there is a data change, and we are given an instance of a View that corresponds to the layout that we passed
* to the constructor, as well as a single ExampleObject instance that represents the current data to bind.
*
* @param v A view instance corresponding to the layout we passed to the constructor.
* @param model An instance representing the current state of a message
*/
@Override
protected void populateView(View v, Post model) {
ButterKnife.bind(this, v);
// populate the list element
exampleView.setText(model.body);
}
@Override
protected List<Post> filters(List<Post> models, CharSequence constraint) {
List<Post> filterList = new ArrayList<>();
for (int i = 0; i < models.size(); i++) {
/* implement your own filtering logic
* and then call filterList.add(models.get(i));
*/
filterList.add(models.get(i));
}
return filterList;
}
@Override
protected Map<String, Post> filterKeys(List<Post> mModels) {
return null;
}
}
これは、GeoQueries に使用している emanuelet のカスタム FirebaseListAdapter です。
public FirebaseListAdapter(Query mRef, Class<T> mModelClass, int mLayout, Activity activity) {
this.mRef = mRef;
this.mModelClass = mModelClass;
this.mLayout = mLayout;
mInflater = activity.getLayoutInflater();
mModels = new ArrayList<>();
mModelKeys = new HashMap<>();
// Look for all child events. We will then map them to our own internal ArrayList, which backs ListView
mListener = this.mRef.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
T model = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(dataSnapshot.getKey(), model);
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(model);
} else {
mModels.add(nextIndex, model);
}
}
notifyDataSetChanged();
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
Log.d(LOG_TAG, "onChildChanged");
// One of the mModels changed. Replace it in our list and name mapping
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
mModels.set(index, newModel);
mModelKeys.put(modelName, newModel);
notifyDataSetChanged();
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.d(LOG_TAG, "onChildRemoved");
// A model was removed from the list. Remove it from our list and the name mapping
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
mModels.remove(oldModel);
mModelKeys.remove(modelName);
notifyDataSetChanged();
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(LOG_TAG, "onChildMoved");
// A model changed position in the list. Update our list accordingly
String modelName = dataSnapshot.getKey();
T oldModel = mModelKeys.get(modelName);
T newModel = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
mModels.remove(index);
if (previousChildName == null) {
mModels.add(0, newModel);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(newModel);
} else {
mModels.add(nextIndex, newModel);
}
}
notifyDataSetChanged();
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.e("FirebaseListAdapter", "Listen was cancelled, no more updates will occur");
}
});
}
public void cleanup() {
// We're being destroyed, let go of our mListener and forget about all of the mModels
mRef.removeEventListener(mListener);
mModels.clear();
mModelKeys.clear();
}
@Override
public int getCount() {
return mModels.size();
}
@Override
public Object getItem(int i) {
return mModels.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
public void remove(String key) {
T oldModel = mModelKeys.get(key);
mModels.remove(oldModel);
mModelKeys.remove(key);
notifyDataSetChanged();
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view == null) {
view = mInflater.inflate(mLayout, viewGroup, false);
}
T model = mModels.get(i);
if (model != null) {
// Call out to subclass to marshall this model into the provided view
populateView(view, model);
}
return view;
}
/**
* Each time the data at the given Firebase location changes, this method will be called for each item that needs
* to be displayed. The arguments correspond to the mLayout and mModelClass given to the constructor of this class.
* <p/>
* Your implementation should populate the view using the data contained in the model.
*
* @param v The view to populate
* @param model The object containing the data used to populate the view
*/
protected abstract void populateView(View v, T model);
public void addSingle(DataSnapshot snapshot) {
T model = snapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(snapshot.getKey(), model);
mModels.add(model);
notifyDataSetChanged();
}
public void update(DataSnapshot snapshot, String key) {
T oldModel = mModelKeys.get(key);
T newModel = snapshot.getValue(FirebaseListAdapter.this.mModelClass);
int index = mModels.indexOf(oldModel);
if (index >= 0) {
mModels.set(index, newModel);
mModelKeys.put(key, newModel);
notifyDataSetChanged();
}
}
public boolean exists(String key) {
return mModelKeys.containsKey(key);
}
@Override
public Filter getFilter() {
if (valueFilter == null) {
valueFilter = new ValueFilter();
}
return valueFilter;
}
protected abstract List<T> filters(List<T> models, CharSequence constraint);
private class ValueFilter extends Filter {
//Invoked in a worker thread to filter the data according to the constraint.
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (mFilteredModels == null) {
mFilteredModels = new ArrayList<>(mModels); // saves the original data in mOriginalValues
mFilteredKeys = new HashMap<>(mModelKeys); // saves the original data in mOriginalValues
}
if (constraint != null && constraint.length() > 0) {
List<T> filtered = filters(mFilteredModels, constraint);
results.count = filtered.size();
results.values = filtered;
mModelKeys = filterKeys(mModels);
} else {
results.count = mFilteredModels.size();
results.values = mFilteredModels;
mModelKeys = mFilteredKeys;
}
return results;
}
//Invoked in the UI thread to publish the filtering results in the user interface.
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint,
Filter.FilterResults results) {
Log.d(LOG_TAG, "filter for " + constraint + ", results nr: " + results.count);
mModels = (List<T>) results.values;
notifyDataSetChanged();
}
}
protected abstract Map<String, T> filterKeys(List<T> mModels);
}
これはモデルクラスの投稿です
public Post() {
// Default constructor required for calls to DataSnapshot.getValue(Post.class)
}
public Post(String uid, String author, String title, String body, String location) {
this.uid = uid;
this.author = author;
this.title = title;
this.body = body;
this.location = location;
}
// [START post_to_map]
@Exclude
public Map<String, Object> toMap() {
HashMap<String, Object> result = new HashMap<>();
result.put("uid", uid);
result.put("author", author);
result.put("title", title);
result.put("body", body);
result.put("starCount", starCount);
result.put("stars", stars);
return result;
}
// [END post_to_map]
}
私はfirebase環境に慣れていないので、これらのGeoQueryはParse構造とはまったく異なります。混乱しています。この問題を解決するのを手伝ってください。私もxmlレイアウトで確認しましたが、正しいです。Logcatにも表示されたリストアイテムの数を正確に示しています。