Sincere apologies for no code, I'm leaving for a wedding and it was either post code or explain my situation.
I've searched Stack and see many posts for Recycler Adapter to Fragment (that created the adapter) interfaces.
A minor few for Fragment(created the adapter) to Adapter posts but they are not nearly as clear.
My situation is this:
On Main Activity, when App is running:
1) Fragment Lyrics (created the REcycler ADapter that is set into a Lyric Recycler View)
2) Fragment Microphone ( speech recognition microphone functionality and XML icon).
What I want to happen is:
user activates Microphone and speaks, that resulting data is passed to the ADAPTER java file and activates a method on the ADAPTER, causing a visual change to RecyclerView Viewholder on the screen.
Yes, I know this is probably bad architecture. It's for a school project, I'm learning, and I've run out of time.
* What I can do so far *
I have activated the pre-made OnClick listerner for the Adapter (when a user clicks on a View) and OnScroll for the RecyclerView (user scrolls, it fires a method in the Adapter that causes the current View to change).
I have made interface for Passing Speech data from Microphone Fragment, through the Main Activity, to the Lyrics Fragment.
On Main, I simply create an instance of the Lyrics Fragment, then call a custom method on Lyrics Fragment that takes the speech data. Something like this.
LyricsFragment.TakeSpeechData(speech data);
* What my plans was...*
When the speech data arrives on Lyrics Fragment, I thought I could just write something like:
MyRecyclerAdapter.SomeMethodOnAdapter (speech data);
I would be home free at this point.
It doesn't work
No go. I get a null pointer exception here. The MyRecyclerAdapter part of the method call is null. I've looked that up and not sure how to fix it.
I'm assuming I'm referencing the original Adapter that was created when the Fragment layed down the RecyclerView and set everything. It's the same global variable for the Adapter on Fragment Lyrics and I'm assuming it "lives on".
I'm sure I'm missing on fundamental Java principles but i don't know what.
I've spent hours and hours on this trying , reading, researching. I'm totally stuck. Please help.
EDIT: Here is my code for VerseFragment (I'm referring to it as "Lyrics" Fragment in my post). Note this Fragment is loaded, created, and functional with recyclerView on screen. Before the user uses the micrphone fragment, which is also on screen, this has already been created.
public class VersesList extends Fragment {
#BindView(R.id.versesRecycleView) RecyclerView versesRecycleView;
#BindView(R.id.songNameTextView) TextView songName;
#BindView(R.id.artistTextView)TextView artistName;
private SharedPreferences mSharedPreferences;
LinearLayoutManager llm;
List verseList;
List finalModVerseList;
public VerseAdapter verseAdapter;
// temporary
private SharedPreferences.Editor editor;
public VersesList() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_verses_list, container, false);
ButterKnife.bind(this, view);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
editor=mSharedPreferences.edit();
displayArtistAndSongName();
lyricsToVerseList();
setVersesIntoRecyclerView();
setVersesScrollListener();
//temp
storeAllVerseLevels();
return view;
}
public static VersesList newInstance(String lyrics){
VersesList versesListFragment = new VersesList();
Bundle args = new Bundle();
args.putString("lyrics", lyrics);
versesListFragment.setArguments(args);
return versesListFragment;
}
public void lyricsToVerseList(){
String lyrics = getArguments().getString("lyrics", "");
verseList = new ArrayList<String>();
finalModVerseList = new ArrayList<String>();
verseList= Arrays.asList(lyrics.split("\n"));
int endOfFinalList=verseList.indexOf("...");
for (int i = 0; i < endOfFinalList; i++) {
if(!verseList.get(i).toString().equals("")){
String addThisVerse = verseList.get(i).toString();
//check on length of verse, if too short add next, check again
int numberOfWords = addThisVerse.split(" ").length;
while (numberOfWords < 10 && i < endOfFinalList) {
i++;
addThisVerse += " " + verseList.get(i).toString();
numberOfWords = addThisVerse.split(" ").length;
}
finalModVerseList.add(addThisVerse);
}
}
}
public void displayArtistAndSongName(){
String song = '"'+mSharedPreferences.getString(SONG_NAME, null)+'"';
String artist = "by "+mSharedPreferences.getString(ARTIST_NAME, null);
songName.setText(song);
artistName.setText(artist);
}
public void setVersesIntoRecyclerView(){
verseAdapter = new VerseAdapter(finalModVerseList, (MainActivity)getActivity(), versesRecycleView);
versesRecycleView.setAdapter(verseAdapter);
llm = new LinearLayoutManager(getActivity(),LinearLayoutManager.HORIZONTAL, false);
versesRecycleView.setLayoutManager(llm);
PagerSnapHelper helper = new PagerSnapHelper();
helper.attachToRecyclerView(versesRecycleView);
}
private void storeLevel(int indexNumber) {
editor.putInt(String.valueOf(indexNumber), 1).apply();
}
private void storeAllVerseLevels(){
for (int i=0; i< finalModVerseList.size();i++){
storeLevel(i);
}
for (int j=0; j< finalModVerseList.size();j++){
String level = String.valueOf(mSharedPreferences.getInt(String.valueOf(j), -1));
Log.d("In Shared Preferences ", "Verse "+j+" Level "+level);
}
}
public void checkSpeech(String text){
List<String> temp = new ArrayList<>();
temp.add("test");
VerseAdapter adapter = new VerseAdapter(temp, (MainActivity)getActivity(), versesRecycleView);
try {
adapter.resetVerse();
}catch (NullPointerException e){
Log.d("Null", e.toString());
}
}
public void setVersesScrollListener(){
versesRecycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == 0) {
verseAdapter.resetVerse();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
}
When you are calling the method from your adapter, is MyRecyclerAdapter an instance or the class? To call someMethodOnAdpater(speechData), you must use an instance . Xia is using an instance.
If you need to call an adapter method from within the fragment in which it was created, you can store it in a variable like this.
MyRecyclerAdapter adapter;
#Override
public View onCreateView(...) {
...
adapter = new MyRecyclerAdapter();
myRecyclerView.setAdapter(adapter);
...
}
public void takeSpeechData(String data) {
adapter.someMethodAdapter(data);
}
Edit:
I'm not sure why the same adapter used by your recyclerview is null after being set. Calling an adapter from is definitely possible (I tested a basic example). The code in my example doesn't differ from what you said you had previously, though. I have upvoted your question for visibility.
**Edit: Add Mic Fragment, it has the interface **
package com.blueoxgym.javainthedark.Fragments;
/**
* A simple {#link Fragment} subclass.
*/
public class MicFragment extends Fragment implements View.OnClickListener {
#BindView(R.id.progressBarMic)
ProgressBar micLevels;
#BindView(R.id.btn_mic)
ImageButton btnMicrophone;
private SpeechRecognizer speech = null;
private Intent recognizerIntent;
public final static String TAG = "In speech mode";
public FragmentManager fragmentManager;
private SharedPreferences mSharedPreferences;
private SharedPreferences.Editor mEditor;
private String trackName;
private String artistName;
private CallMainLoadVerseFragment loadVerseFragment;
private CheckSpeech checkSpeechOnVerse;
public MicFragment() {
// Required empty public constructor
}
public static MicFragment newInstance (){
MicFragment micFragment = new MicFragment();
return micFragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_mic, container, false);
ButterKnife.bind(this, view);
this.loadVerseFragment = (CallMainLoadVerseFragment) getActivity();
this.checkSpeechOnVerse = (CheckSpeech) getActivity();
btnMicrophone.setOnClickListener(this);
fragmentManager = getFragmentManager();
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
mEditor = mSharedPreferences.edit();
return view;
}
#Override
public void onClick(View v) {
if (v == btnMicrophone) {
startSpeechToText();
}
}
class listener implements RecognitionListener {
...
#Override
public void onResults(Bundle results) {
String str = new String();
Log.d(TAG, "onResults " + results);
ArrayList<String> data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
String text = data.get(0).toLowerCase().replace("by","");
Fragment currentFragment = fragmentManager.findFragmentById(R.id.content_frame);
if (currentFragment.toString().contains("LyricSearch")){
searchForSong(text);
} else if (currentFragment.toString().contains("VersesList")){
-----------> Here it is called checkSpeechOnVerse.checkingSpeech(text);
}
}
}
public void startSpeechToText(){
btnMicrophone.setBackgroundResource(R.drawable.circle_green);
speech=SpeechRecognizer.createSpeechRecognizer(getContext());
speech.setRecognitionListener(new listener());
recognizerIntent= new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "en-US");
recognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getActivity().getPackageName());
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 5);
speech.startListening(recognizerIntent);
}
...
...
public interface CheckSpeech {
void checkingSpeech (String text);
}
}
MainActivity, implements CheckSpeech Interface
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, MicFragment.CallMainLoadVerseFragment, MicFragment.CheckSpeech {
....
...
#Override
public void checkingSpeech(String text) {
VersesList versesList = new VersesList();
--------> Now, I'm pass data from Main to VersesList Fragment(it has the original Adapter)
versesList.checkSpeech(text);
}
VersesList Fragment, where I try to call Adapter
public class VersesList extends Fragment {
....
private VerseAdapter verseAdapter;
setVersesIntoRecyclerView();
....
<---ADAPTER IS MADE AND SET HERE----.
public void setVersesIntoRecyclerView(){
verseAdapter = new VerseAdapter(finalModVerseList, (MainActivity)getActivity(), versesRecycleView);
versesRecycleView.setAdapter(verseAdapter);
llm = new LinearLayoutManager(getActivity(),LinearLayoutManager.HORIZONTAL, false);
versesRecycleView.setLayoutManager(llm);
PagerSnapHelper helper = new PagerSnapHelper();
helper.attachToRecyclerView(versesRecycleView);
}
public void checkSpeech(String text){
-------> NPE NPE
versesAdapter.someMethodOnAdapter(text);
}
public void setVersesScrollListener(){
versesRecycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == 0) {
BUT THIS WORKS!!! No NPE. --------> verseAdapter.resetVerse();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// VerseAdapter.VerseViewHolder view = versesRecycleView.findViewHolderForAdapterPosition(llm.findFirstVisibleItemPosition());
}
});
}
If you need to call an adapter method from within the fragment in which it was created, you can store it in a variable within that fragment.
MyRecyclerAdapter adapter;
#Override
public View onCreateView(...) {
...
adapter = new MyRecyclerAdapter();
myRecyclerView.setAdapter(adapter);
...
}
public void takeSpeechData(String data) {
adapter.someMethodAdapter(data);
}
Then you can call that method directly from another fragment. (link to accessing fragments)
VersesList versesList = (VersesList) getActivity().getSupportFragmentManager.findFragmentById(containerId);
versesList.takeSpeechData("data");
gif of example
Related
In the parent activity, I have an edit text in a toolbar, and user can make a search through the data displayed by the recyclerview.
When the user push enter key down, the string in the edittext is sent to the fragment by :
Bundle bundle = new Bundle();
bundle.putString(predResult, placeid);
MapFragment mapFragment = new MapFragment();
ListRestFragment listRestFragment = new ListRestFragment();
mapFragment.setArguments(bundle);
listRestFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.map, mapFragment)
.replace(R.id.list_frag, listRestFragment)
.commit();
but, unfortunatly, the recyclerview is not resfreshed while my adapter is notified the data is changed as it shown below:
private void queryToList(Query query) {
query.addSnapshotListener((queryDocumentSnapshots, e) -> {
restaurantList = queryDocumentSnapshots.toObjects(Restaurant.class);
if (!myPrediction.contains("myPrediction")) {
System.out.println(myPrediction);
for (Restaurant item : restaurantList) {
if (item.getRestaurantID().contains(myPrediction)) {
restaurantListPred = new ArrayList<>();
restaurantListPred.add(item);
updateUI(restaurantListPred);
}
}
} else updateUI(restaurantList);
});
}
private void updateUI(List<Restaurant> restaurants) {
configureFab();
configureRecyclerView(restaurants);
}
private void configureRecyclerView(List<Restaurant> restaurant) {
this.adapter = new RestaurantAdapterClassic(restaurant);
this.recyclerView.setAdapter(this.adapter);
this.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(new SimpleItemDecorator(getContext()));
}
the new List is updated automatically when the user makes his request, but the recyclerView doesn't display the new data.
if you want to check my adapter implementation:
public class RestaurantAdapterClassic extends RecyclerView.Adapter<RestaurantViewHolder> {
private List<Restaurant> restaurantsList;
// CONSTRUCTOR
public RestaurantAdapterClassic(List<Restaurant> restaurants) {
this.restaurantsList = restaurants;
}
#Override
public RestaurantViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// CREATE VIEW HOLDER AND INFLATING ITS XML LAYOUT
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.listview_pattern, parent, false);
return new RestaurantViewHolder(view);
}
#Override
public void onBindViewHolder(RestaurantViewHolder viewHolder, int position) {
viewHolder.updateWithRestaurant(this.restaurantsList.get(position));
}
// RETURN THE TOTAL COUNT OF ITEMS IN THE LIST
#Override
public int getItemCount() {
return this.restaurantsList.size();
}
public Restaurant getRestaurant(int position) {
return this.restaurantsList.get(position);
}
public void filterList(List<Restaurant> filteredList) {
restaurantsList = filteredList;
notifyDataSetChanged();
}
}
where is my error or my misunderstanding?
EDIT SOLUTION -
Create an Interface
Actually, to send the new data data from my Parent Activity to my Fragment with a listener to observe when the data changes.
Keep The data reference sent to the adapter
Actually, the main big problem that I had was my adapter doesn't refresh when I sent new array. The reason was an adapter creates a reference with the list/array. if you want to refresh it, you need to keep this reference by get the list, erase it, and put/add new data inside by the method addALL for example.
First of all, adapter.notifyDataSetChanged(); doesn't have any effect in the code as inside updateUI you create the adapter every time you call it.
I think, the main problem is in here:
if (item.getRestaurantID().contains(myPrediction)) {
restaurantListPred = new ArrayList<>();
restaurantListPred.add(item);
updateUI(restaurantListPred);
}
This block doesn’t execute at all. Thats why the list not updated.
My database (Firestore) stores Charts, created by Users.
On opening the app, the User is shown their own Charts, sorted by Chart Name (this is displayed in a Fragment called MyChartsFragment). They can click a button to change the sort field; this should retrieve the same list of Charts, but sorted by the selected field.
I'm able to pass the new sort field name into MyChartsFragment, but at that point the list of Charts goes blank. I assume I need to redraw it somehow.
The MyChartsFragment is as follows (I've removed most of the try-catch blocks, error checks etc, for clarity):
public class FragmentMyCharts extends FragmentChartsList {
public FragmentMyCharts() {}
public Query getQuery(FirebaseFirestore databaseReference, String orderBy) {
// Specify the query which is used to retrieve this user's charts
return databaseReference.collection("charts")
.whereEqualTo("uid", getUid())
.orderBy(orderBy);
}
}
This extends FragmentChartsList, which contains:
public abstract class FragmentChartsList extends Fragment {
private FirebaseFirestore mDatabaseRef;
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
private String mOrder = "name";
FragmentManager mFragmentManager;
ToolbarFragmentListCharts mFragmentTLC;
public FragmentChartsList() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout, and find Recycler View to hold the list
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database, and get the appropriate query
mDatabaseRef = FirebaseFirestore.getInstance();
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add listener to charts collection, and deal with any changes by re-showing the list
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null && queryDocumentSnapshots.isEmpty()) {
((MainActivity) Objects.requireNonNull(getActivity())).setPage(1);
}
}
});
}
#Override
public void onStart() {
super.onStart();
mAdapter.startListening();
}
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
}
public abstract Query getQuery(FirebaseFirestore databaseReference, String orderBy);
}
So, when the main activity is notified by the toolbar that the sort field has changed, it retrieves the MyChartsFragment (as a FragmentChartsList) and calls the setOrderField function:
public void setOrder(String order) {
FragmentChartsList myCharts = (FragmentChartsList) mPagerAdapter.getItem(0);
myCharts.setOrderField(order);
}
This appears to update the value of mOrder correctly, but then it doesn't re-display the RecyclerView, so I just get a blank space.
What am I doing wrong?
EDITED TO ADD DETAIL:
My ChartListAdapter class is:
public class ChartListAdapter
extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
private Activity mActivity;
private FirestoreRecyclerOptions<Chart> recyclerOptions;
public ChartListAdapter(FirestoreRecyclerOptions<Chart> recyclerOptions, Activity activity) {
super(recyclerOptions);
mActivity = activity;
}
#Override
protected void onBindViewHolder(#NonNull ChartViewHolder holder, int position, #NonNull Chart model) {
final String chartKey = this.getSnapshots().getSnapshot(position).getId();
model.setKey(chartKey);
// Set click listener for the chart
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, ActivityViewChart.class);
intent.putExtra("ChartKey", chartKey);
mActivity.startActivity(intent);
}
});
// Implement long-click menu
mActivity.registerForContextMenu(holder.itemView);
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#NonNull
#Override
public ChartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
}
rather then recreating the recyclerView adapter, try updating the options in the adapter.
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
// Use Recycler Adapter in RecyclerView
mAdapter.setRecyclerOptions(recyclerOptions);
// then call the notify data set changed on the adapter to recreate the recyclerView elements
mAdapter.notifyDataSetChanged();
}
and in the recyclerView adapter class, add a setter for the recyclerOptions :
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
Found one problem... I had declared mAdapter with:
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
when it should have been:
private ChartListAdapter mAdapter;
Solved... in setOrderField I was declaring a new mAdapter, but not listening to it. Once I added
mAdapter.startListening()
it all worked fine!
UPDATE 2021:
I don't know, for how long this new method works, but I'm so thankful for finding it out by myself, because I couldn't find any good response to that:
You don't have to create any Methods in your Adapter anymore.
Simply create the setOrder Method like this:
public FirestoreRecyclerOptions<event_log_Item> getOrder(String order){
String mOrder = order;
Query query = current_productRef.orderBy(mOrder, Query.Direction.DESCENDING);
FirestoreRecyclerOptions<event_log_Item> options = new FirestoreRecyclerOptions.Builder<event_log_Item>().setQuery(query, event_log_Item.class).build();
return options;
}
This Method will return the order-options for the field you want.
Then simply do:
mAdapter.updateOptions(setOrder("Timestamp"));
mAdapter.notifyDataSetChanged();
The Method "updateOptions" is already implemented. Just simply give it the option you want and add "notifyDataSetChanged();"
I'm retrieving data using a FirestoreRecyclerAdapter, and, on completion, I need to check whether any items have been retrieved or not. I can't figure out how to do this.
I'm calling it from a class called FragmentChartsList, shown below. This should set up the adapter initially, with "name" as the value for mOrder. Later, the Activity which contains this Fragment can call setOrderField() with a different value of mOrder, which the user has selected from a Spinner.
Each time setOrderField() is called, a new adapter instance is created and attached to the recyclerView. At this point I need to check whether the new version of the adapter contains any data, and either show a "no Charts found" message, or show the Charts which were retrieved (obviously if the list is just being sorted, then the number of items remains the same, but I'm going to be expanding this to allow the user to filter the Charts by different criteria, so the number of Charts returned will change).
Currently, setOrderField() calls refreshViewOnNewData(), which should find out how many Charts are being shown; if it's 0, it should show the "no Charts found" message, and if it's >0 it should show the RecyclerView containing the Charts.
At the moment, I'm always getting a value of 0 when I try to count the Charts. I suspect it's because the adapter hasn't finished retrieving them from the database yet, but I can't find anything that allows me to add some kind of "onComplete" listener so that I know it's finished.
Can anyone suggest how I can achieve this?
public abstract class FragmentChartsList extends Fragment {
private FirebaseFirestore mDatabaseRef;
private ChartListAdapter mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
private String mOrder = "name";
private TextView mLoadingList, mEmptyList;
public FragmentChartsList() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
mLoadingList = rootView.findViewById(R.id.loading_list);
mEmptyList = rootView.findViewById(R.id.empty_list);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database
mDatabaseRef = FirebaseFirestore.getInstance();
setOrderField(mOrder); // Initialised to "name"
return rootView;
}
#Override
public void onStart() {
super.onStart();
mAdapter.startListening();
}
#Override
public void onStop() {
super.onStop();
mAdapter.stopListening();
}
#Override
public void onDestroy() {
super.onDestroy();
mAdapter.stopListening();
}
// HELPER FUNCTIONS
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Update recycler options
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, getActivity());
mAdapter.startListening();
mRecycler.swapAdapter(mAdapter, true);
refreshViewOnNewData();
}
private void refreshViewOnNewData() {
// Hide "loading" text
mLoadingList.setVisibility(View.GONE);
// Check number of charts being shown
//if (mAdapter != null && (mAdapter.getCount() > 0)) {
// If > 0, show Charts
mEmptyList.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
// If number of Charts = 0
// show "no charts"
mEmptyList.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
The adapter class looks like this:
public class ChartListAdapter extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
private Activity mActivity;
private int mCount;
public ChartListAdapter(FirestoreRecyclerOptions<Chart> recyclerOptions, Activity activity) {
super(recyclerOptions);
mActivity = activity;
}
#Override
protected void onBindViewHolder(#NonNull ChartViewHolder holder, int position, #NonNull Chart model) {
final String chartKey = this.getSnapshots().getSnapshot(position).getId();
model.setKey(chartKey);
// Set click listener for the chart
// On click, the user can view the chart
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, ActivityViewChart.class);
intent.putExtra("ChartKey", chartKey);
mActivity.startActivity(intent);
}
});
// Implement long-click menu
mActivity.registerForContextMenu(holder.itemView);
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#NonNull
#Override
public ChartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
#Override
public void onDataChanged() {
super.onDataChanged();
mCount = getItemCount();
}
public int getCount() {
return mCount;
}
}
Figured this out... I needed to set a listener on the query instead.
So, instead of having the call to refreshViewOnNewData from setOrder above, I now have:
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null) {
mLoadingList.setVisibility(View.GONE);
if(queryDocumentSnapshots.size() > 0) {
mEmptyList.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
}else {
mEmptyList.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
});
}
Also removed mCount from the adapter class, along with getCount and onDataChanged
I am developing an android application where i am using a RecyclerView to display a list of items.I am getting the list from server as json.So my problem is within this list i am getting another list as item.That is if my main arraylist contain title and materials, the material is another arraylist.So can you please suggest a solution to display a list within recyclerview.
The code below is my adapter
public class CurriculumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private ArrayList<Curriculum> mArrayListCurriculum;
public CurriculumAdapter(Context mContext, ArrayList<Curriculum> mArrayListCurriculum) {
this.mContext = mContext;
this.mArrayListCurriculum = mArrayListCurriculum;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_key_features, parent,false);
return new KeyFeatureViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof KeyFeatureViewHolder) {
((KeyFeatureViewHolder) holder).mTextViewFeatureTitle.setText(mArrayListCurriculum.get(position).getTitle());
}
}
#Override
public int getItemCount() {
return mArrayListCurriculum == null ? 0 : mArrayListCurriculum.size();
}
public static class KeyFeatureViewHolder extends RecyclerView.ViewHolder {
public TextView mTextViewFeatureTitle;
public KeyFeatureViewHolder(View itemView) {
super(itemView);
mTextViewFeatureTitle = (TextView) itemView.findViewById(R.id.txtFeature);
}
}
}
The code below is my fragment with dummy arraylist data
public class CourseCurriculumFragment extends Fragment {
private FragmentInterface mFragmentInterface;
private ArrayList<Curriculum> mArrayListCurriculum;
private ArrayList<Material> mArrayListMaterial;
private RecyclerView mRecyclerViewCurriculum;
private LinearLayoutManager mLinearLayoutManager;
private CurriculumAdapter mCurriculumAdapter;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_course_curriculum, container, false);
return view;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
}
private void initView(View view) {
mArrayListMaterial = new ArrayList<>();
mArrayListCurriculum = new ArrayList<>();
populateMaterials();
populateKeyFeatures();
mRecyclerViewCurriculum = (RecyclerView) view.findViewById(R.id.recyclerViewCurriculum);
mCurriculumAdapter = new CurriculumAdapter(getActivity(), mArrayListCurriculum);
mLinearLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerViewCurriculum.setLayoutManager(mLinearLayoutManager);
mRecyclerViewCurriculum.setAdapter(mCurriculumAdapter);
mRecyclerViewCurriculum.setItemAnimator(new DefaultItemAnimator());
}
private void populateMaterials() {
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
}
private void populateKeyFeatures() {
mArrayListCurriculum.add(new Curriculum("UNIT 1",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 2",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 3",mArrayListMaterial));
}
}
A bind method in a holder is a good way to pass data to it.
In your case this bind method should take in a Curriculum and a Material object as parameters.
Inside the onBindViewHolder method of the adapter, instead of reaching into the variables of the holder, you should call this bind method.
In the implementation of the method inside the you KeyFeatureViewHolder class you should use these passed parameters and display them in the appropriate UI elements.
Lastly, to get the Material object data into adapter, add ArrayList<Material> as a constructor parameter just like you did with Curriculum.
Use RecyclerView with header, title as header and materials as items of that header. Look at this example.
You need to design a custom list for yourself. For example take an object like this.
public class ListItem {
public curriculumName = null;
public materialName = null;
}
Now populate this list after you parse the JSON string. Get your first Curriculum and populate the object like this
private ArrayList<ListItem> mListItemArray = new ArrayList<ListItem> ();
for(curriculum : mArrayListCurriculum) {
ListItem mListItemHead = new ListItem();
mListItemHead.curriculumName = curriculum.getName();
// Set the header here
mListItemArray.add(mListItemHead);
for(material : curriculum.getMaterials()){
ListItem mListItem = new ListItem();
mListItem.materialName = material.getName();
// Add materials here
mListItemArray.add(mListItem);
}
}
Now, you've a list with headers and materials. When the materialName in your mListItemArray is null, it identifies that this is a header and vice versa.
Now the trick is to modify your adapter of your RecyclerView so that you can bind proper view to your items in your list.
You can find an indication from this answer on how you can achieve this desired behaviour.
Basically, the idea is to modify your getItemViewType to pass the proper view in your onBindViewHolder. Your getItemViewType might look like this.
#Override
public int getItemViewType(int position) {
if (mListItemArray.get(position).curriculumName != null) {
// This is where we'll add header.
return HEADER_VIEW;
}
return super.getItemViewType(position);
}
I saw all the questions which is similar to my question ( in this , this , this and this link )
I had myAdapter.notifyDataSetChanged() in my Activity but it doesn't work
I have 3 classes,
DBHelper - For storing and getting Database contents ( NO ISSUES HERE )
SimpleRecyclerAdapter - Adapter for RecyclerList
ThirdActivity
What i did in ThirdActivity :
I have TextBox to get data and store it in Database and a Button. In
the Onclicklistener of Button, i specified code to
get text from textbox
add it into table using DBHelper
retrive data as ArrayList from DBHelper
myAdapter.notifyDataSetChanged()
When i click the Button, I got Data in LogCat which i specified inside OnclickListener but it is not reflected to the listview.
Here is my code,
ThirdActivity:
public class ThirdActivity extends AppCompatActivity{
private DrawerLayout mDrawerLayout;
DbHelper dbHelper;
EditText et;
Button addButton;
RecyclerView rv;
ArrayList<String> myNotesList;
SimpleRecycler3Adapter adapter3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.thirdactivity);
myNotesList = new ArrayList<>();
et=(EditText) findViewById(R.id.et);
addButton=(Button)findViewById(R.id.addButton);
rv = (RecyclerView) findViewById(R.id.dbListrv);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getBaseContext());
rv.setLayoutManager(linearLayoutManager);
rv.setHasFixedSize(true);
adapter3 = new SimpleRecycler3Adapter(myNotesList);
rv.setAdapter(adapter3);
dbHelper = new DbHelper(this, null, null, 1);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("DB", "Constructor");
String note=et.getText().toString();
dbHelper.addNote(note);
printData();
}
});
}
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
}
SimpleRecyclerViewAdapter :
public class SimpleRecycler3Adapter extends RecyclerView.Adapter<SimpleRecycler3Adapter.NotesHolder> {
private ArrayList<String> myNotesList=new ArrayList<String>();
String TAG="ThirdAdapter kbt";
RecyclerView rv;
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
int i = 0;
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Log.d(TAG,"finish");
}
#Override
public NotesHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
Log.d(TAG,"On create started");
View view2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerlist3_item, viewGroup, false);
Log.d(TAG,"ADAP STAR ONCR second switch 2nd line");
NotesHolder viewHolder2 = new NotesHolder(view2);
Log.d(TAG,"ADAP STAR ONCR second switch 3nd line");
return viewHolder2;
}
#Override
public void onBindViewHolder(NotesHolder notesHolder, int i) {
Log.d(TAG, "ONBIND SECOND i value is " + i);
// notesHolder.thumbnail.setImageResource(R.drawable.placeholder);
notesHolder.dblistitem.setText(myNotesList.get(i));
Log.d(TAG,"ONBIND second title issssss");
}
#Override
public int getItemCount() {
return myNotesList.size();
}
class NotesHolder extends RecyclerView.ViewHolder {
protected ImageView thumbnail;
protected TextView dblistitem;
public NotesHolder(View itemView) {
super(itemView);
Log.d(TAG, "JSON Inside HOLDER");
rv=(RecyclerView)itemView.findViewById(R.id.dbListrv);
// thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
dblistitem = (TextView) itemView.findViewById(R.id.dblistitem);
}
}
}
You're not updating the myNotesList that is in adapter class but in activity class. But the adapter uses it's local myNotesList.
So on button click, update myNotesList of adapter with latest data available and notify the adapter.
EDIT
Pass the latest data to adapter. Have this method in adapter class and call this before notifyDataSetChanged();
public void updateNotes(ArrayList<String> notesList) {
myNotesList = notesList;
}
1.you are intializing your dbhelper after setting adapter to listview so it couldn't contain any data initially
2.for updating recycler view data list do as follows
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
You have a problem in your SimpleRecyclerViewAdapter, just change this:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
For this:
myNotesList = myList;
And in your activity's printData() change:
myNotesList=dbHelper.databasetostring();
for this:
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
Explanation:
First you initialize myNotesList variable:
myNotesList = new ArrayList<>();
Then you initialize adapter3
adapter3 = new SimpleRecycler3Adapter(myNotesList);
But your adapter is not saving the reference, instead you're copying its data into another variable:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Doing that, if you change myNotesList variable in your activity will not modify your adapter's dataset.
In your method printData() you change myNotesList variable. Which will not touch your adapter or its data
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
You can't change myNotesList by changing myList.
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
// int i = 0;
// while (i < myNotesList.size()) {
// myNotesList.add(myList.get(i).toString());
// }
this.myNotesList = myList;
Log.d(TAG,"finish");
}
Not a good idea to call notifyDataSetChanged() when you know exactly what changed in your data collection.
See this implementation here.
They have even documented to use notifyDataSetChanged() as a last resort in this doc.
You get nice animations for free if you use methods like notifyItemInserted() and the rest.
Also do not go on replacing the collection object entirely, see the implmentation link that has been attached.