When I open a Fragment I try to show a ProgressBar, populate a RecyclerView, update NotifyChange to RecyclerView and then hide the ProgressBar.
Right now the ProgressBar isn't spinning. I think it has to do with SetRecyclerViewItems being executed on the UI Thread but if I don't do that I get an error saying that the UI can only be updated in the UI thread.
Fragment:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
/* Initializing elements code removed */
SetRecyclerView();
return view;
}
public async override void OnStart()
{
base.OnStart();
ShowProgressBar();
await LoadAsync();
HideProgressBar();
}
private async Task LoadAsync()
{
await Task.Run(() => {
SetRecyclerViewItems();
});
}
private void SetRecyclerView()
{
mLayoutManager = new LinearLayoutManager(mRecyclerView.Context);
mRecyclerView.SetLayoutManager(mLayoutManager);
mAdapter = new TransactionsRecyclerAdapter(this.Activity, mTransactions, dateFormat);
mAdapter.ItemClick += MAdapter_ItemClick;
mRecyclerView.SetAdapter(mAdapter);
}
private void SetRecyclerViewItems(List<PaymentListItemViewModel> transactions = null)
{
Activity.RunOnUiThread(() => {
if (transactions == null)
transactions = GetTransactions();
mAdapter.NotifyChange(transactions);
SetTotal();
});
}
Adapter.NotifyChange
public void NotifyChange(List<PaymentListItemViewModel> transactions)
{
mTransactions = transactions;
NotifyDataSetChanged();
}
Why isn't the ProgressBar spinning?
Is the way I'm populating new data to the Adapter correct (sending in a new list and then NotifyDataSetChanged?
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/login_background">
<RelativeLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:gravity="center"
android:layout_centerInParent="true"
android:id="#+id/transactionsProgressBar">
<ProgressBar
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="#android:style/Widget.ProgressBar.Large" />
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/linearLayout4"
android:layout_weight="1">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerViewTransactions"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scrollbars="vertical" />
</LinearLayout>
</LinearLayout>
In OnCreateView:
mLayoutProgressBar = view.FindViewById<RelativeLayout>(Resource.Id.transactionsProgressBar);
Show/Hide ProgressBar:
private void ShowProgressBar()
{
mLayoutProgressBar.Visibility = ViewStates.Visible;
}
private void HideProgressBar()
{
mLayoutProgressBar.Visibility = ViewStates.Gone;
}
You put the loading onto a separate thread by using Task.Run() but in there you marshal back to the UI thread to update your adapter (Activity.RunOnUiThread()), this will block the UI thread and your spinner stops running.
Depending on what your SetTotal() method is doing, you should only have the call to mAdapter.NotifyChange(transactions); being executed on the UI thread.
Related
Creating a todo list using firebase, confused trying to understand the logic behind it please see the main actitivty below.\Creating a todo list using firebase, confused trying to understand the logic behind it please see the main actitivty below.Creating a todo list using firebase, confused trying to understand the logic behind it please see the main actitivty below
public class MainActivity extends AppCompatActivity {
List<ToDo> toDoList = new ArrayList<>();
FirebaseFirestore database;
//
RecyclerView listItem;
RecyclerView.LayoutManager layoutManager;
FloatingActionButton fab;
public MaterialEditText title,description;
public boolean isUpdate = false;
public String idUpdate = " ";
ListItemAdapter adapter;
SpotsDialog dialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = FirebaseFirestore.getInstance();
//i think the issue is here
listItem = (RecyclerView)findViewById(R.id.listTodo);
listItem.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
listItem.setLayoutManager(layoutManager);
#Override
public void onClick(View view) {
if(!isUpdate){
setData(title.getText().toString(), description.getText().toString());
}
else{
updateData(title.getText().toString(),description.getText().toString());
isUpdate = !isUpdate; //this resets it
}
}
});
}
#Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getTitle().equals("DELETE"))
deleteItem(item.getOrder());
return super.onContextItemSelected(item);
}
private void loadData() {
dialog.show();
if (toDoList.size() > 0)
toDoList.clear();
database.collection("ToDoList")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
//end of oncomplete section added
for (DocumentSnapshot doc:task.getResult())
{
ToDo todo = new ToDo(doc.getString("id"),
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MainActivity.this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/layout_info"
android:orientation="vertical"
android:padding="16dp"
android:background="#1190CB"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.rengwuxian.materialedittext.MaterialEditText
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:text="Title"
android:textColorHint="#android:color/white"
android:textSize="30sp"
app:met_baseColor="#android:color/white"
app:met_floatingLabel="highlight"
app:met_primaryColor="#android:color/white"
app:met_singleLineEllipsis="true" />
<!--add more for card items -->
<com.rengwuxian.materialedittext.MaterialEditText
android:id="#+id/description"
android:text="Description"
android:textSize="20sp"
android:inputType="textMultiLine"
android:textColorHint="#android:color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:met_baseColor="#android:color/white"
app:met_floatingLabel="highlight"
app:met_primaryColor="#android:color/white"
app:met_singleLineEllipsis="true"/>
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:src="#drawable/ic_add_black_24dp"
android:layout_marginRight="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
app:layout_anchor="#id/app_bar_layout"
app:layout_anchorGravity="bottom|right"
app:fabSize="normal"/>
</android.support.design.widget.CoordinatorLayout>
In onCreate of your activiy, try to first init the layoutManager and only then link it to the recyclerView:
layoutManager = new LinearLayoutManager(this);
listItem.setLayoutManager(layoutManager);
Another option is to declare it in the xml file:
xmlns:app="http://schemas.android.com/apk/res-auto"
<android.support.v7.widget.RecyclerView
android:id="#+id/listTodo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
and then you won't have to do anything programmatically.
Update from comment:
Your app is crashing with NullPointerException because you don't initiate todoList in your adapter's constructor, so the getItemCount() is called when your list is null.
Add a check in that method, in the adapter class:
#Override
public int getItemCount() {
if (todoList != null)
return todoList.size();
return 0;
}
You missing setting layout manager for your recycler view. You have commented creating him
listItem.setLayoutManager(manager);
remove the loadData() inside of the loadData().
Try to set the layout manager this way.
listItem = (RecyclerView)findViewById(R.id.listTodo);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
listItem.setLayoutManager(layoutManager);
listItem.setHasFixedSize(true);
Try to use the firebase recycler view, will help you to manage the new items, updates and etc.
https://github.com/firebase/FirebaseUI-Android
I have a recyclerview in my app showing a list of content. If a user is subscribed, they can proceed to read the full content and if not the recyclerview is hidden and a subscribe layout is shown.
Layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/light_grey"
android:orientation="vertical"
android:weightSum="10"
tools:layout_editor_absoluteY="25dp">
<!--RECYCLER VIEW-->
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_weight="8.3"
android:padding="2dp"
android:visibility="gone" />
<!--SUBSCRIBE TO VIEW CONTENT LAYOUT-->
<RelativeLayout
android:id="#+id/rlSubscribeToView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:visibility="gone"
android:layout_margin="10dp"
android:elevation="48dp"
android:layout_gravity="center"
android:layout_weight="8.3">
<TextView
android:id="#+id/tvSubscribe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_centerInParent="true"
android:textColor="#color/maroon"
android:text="#string/subscribe_to_view"/>
<Button
android:id="#+id/btnSubscribe"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginTop="15dp"
android:layout_marginStart="20dp"
android:layout_below="#id/tvSubscribe"
android:layout_marginEnd="20dp"
android:text="#string/subscribe"
android:textColor="#color/white" />
</RelativeLayout>
and the
RecyclerView Adapter
public void onBindViewHolder(#NonNull final ReadViewholder, final int position) {
holder.readMoreButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (userIsSubscribed) {
//Launch the next Activity
} else {
//Show the subscribe layout
holder.rlSubscribeToView .setVisibility(View.VISIBLE);
//Then hide the entire recyvlerView
}
}
});
}
class ReadViewholder extends RecyclerView.ViewHolder {
RelativeLayout rlSubscribeToView;
Button readMoreButton;
ReadViewholder(#NonNull View itemView) {
super(itemView);
//Not able to find (rlSubscribeToView ) since its not inside the recyclerview
rlSubscribeToView = itemView.findViewById(R.id.rlSubscribeToView);
readMoreButton= itemView.findViewById(R.id.readMoreButton);
}
}
How can I get access to the subscribe layout (rlSubscribeToView), which is on the same layout file as the recycler view and also hide the entire recycler view in the Adapter?
From a little bit of research, the are 2 callbacks that get can get you a reference to the actual RecyclerView, the onAttachedToRecyclerView and the onDetachedFromRecycler methods. My guess is that you are calling the Adapter constructor and passing in a context. If so use the below code, it will produce your desired result.
RelativeLayout rlSubscribeToView;
RecyclerView recyclerView;
public RecyclerAdapter(Context context) {
this.context = context;
this.videoItems = videoItems;
rlSubscribeToView = ((Activity) context).findViewById(R.id.rlSubscribeToView);
}
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
and in your onBindViewHolder you can now get access the subscribe layout
public void onBindViewHolder(#NonNull final ReadViewholder, final int position) {
...
rlSubscribeToView.setVisibility();
}
What you need is an interface passed in as a parameter when you create your Adapter.
Example:
class Adapter(private val actions: Actions) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// Create ViewHolder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Setup Binder
holder.readMoreButton.setOnClickListener(View.OnClickListener {
if (userIsSubscribed) {
actions.launchActivity() //Launch the next Activity
} else {
//Show the subscribe layout
holder.rlSubscribeToView.setVisibility(View.VISIBLE)
actions.hideRecylerView() //Then hide the entire recyclerView
}
})
}
}
internal interface Actions {
fun launchActivity()
fun hideRecylerView()
}
How to handle empty data View in a RecyclerView ,I have tried so many ways from internet but none seems to work. I am also using realm database so I don't know if this is the right way to check if it is empty or not.
this is my Fragment xml where the RecyclerView is located
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_no_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_green_dark"
android:gravity="center"
android:text="emptty"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="#android:color/white"
android:visibility="invisible" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_favorite"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
and this is my fragment class
public class FavouriteFragment extends Fragment {
RecyclerView mRecyclerView;
FavouriteAdapter adapter;
Realm mRealm;
int positions;
TextView emptyText;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.favourite_fragment,container,false);
mRealm=Realm.getDefaultInstance();
RealmQuery<news> quotesRealmQuery = mRealm.where(News.class).equalTo("favourite",true);
RealmResults<News> mResults = newsRealmQuery.findAll();
emptyText= (TextView) view.findViewById(R.id.tv_no_data);
//adapter=new FavouriteAdapter(getActivity(),mResults,mRealm);
mRecyclerView= (RecyclerView) view.findViewById(R.id.rv_favorite);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
if (!mResults.isEmpty()) {
//if data is available, don't show the empty text
emptyText.setVisibility(View.INVISIBLE);
adapter=new FavouriteAdapter(getActivity(),mResults,mRealm,pos,single);
mRecyclerView.setAdapter(adapter);
mRealm.addChangeListener(new RealmChangeListener<Realm>() {
#Override
public void onChange(Realm element) {
adapter.notifyItemRemoved(positions);
}
});
} else
emptyText.setVisibility(View.VISIBLE);
return view;
}
Your RecyclerView is not transparent so you should remember to hide it when is empty:
if (!mResults.isEmpty()) {
//if data is available, don't show the empty text
emptyText.setVisibility(View.INVISIBLE);
mRecyclerView.setVisibility(View.VISIBLE);
} else {
emptyText.setVisibility(View.VISIBLE);
mRecyclerView.setVisibility(View.GONE);
}
I'm trying to add pull to refresh functionality into listview.
In fragment_page.xml (here is defined global listview)
<LinearLayout
style="#style/AppTheme.BaseActivity"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context="xx.view.fragment.PageFragment">
<TextView
android:id="#android:id/empty"
style="#style/AppTheme.WrapContent.NoResult"
android:text="#string/no_results"/>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="#drawable/list_selector"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
And in Activity:
public class MessageActivity extends TabBaseActivity implements ManageView, OnRefreshListener {
private MessageController mMessageController;
private int mSelectedTab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
setUp(new String[]{"Unread", "Read", "Sent", "All"}, Constants.Adapter.MESSAGE_ADAPTER);
mMessageController.loadMessages();
//START Swipe to refresh
try {
final SwipeRefreshLayout swipeContainer = (SwipeRefreshLayout) findViewById(R.id.swiperefresh);
if(swipeContainer == null) {
Logger.d("Null");
}
swipeContainer.setEnabled(false);
swipeContainer.setOnRefreshListener(this);
} catch (Exception e) {
Logger.e(e.getMessage());
}
//END Swipe to refresh
}
But swipeContainer is still null.
I tried to to it using the following tutorials:
http://www.androidhive.info/2015/05/android-swipe-down-to-refresh-listview-tutorial/
and
http://www.survivingwithandroid.com/2014/05/android-swiperefreshlayout-tutorial.html
But without the luck.
How can i solve it please?
Many thanks for any advice.
EDIT:
Content for view is set in init:
private void init() {
mMessageController = MessageController.getInstance(this, getSupportFragmentManager());
mMessageController.setView(this);
mSelectedTab = 0;
}
If you want to use your views in fragment (SwipeRefreshLayout, etc.), initialize your views in onActivityCreated() method. Once onActivityCreated() was called, you knew the activity was ready to be called upon.
I have a rather popular scenario in which there is a fragment containing a progress bar and a ListView. I'd like to populate the ListView after fetching the data using Retrofit. Now, the progress bar needs to be made invisible once a response comes back in.
Would it be a good idea to store the view of the fragment layout inflated in onCreateView() as a member variable and use that variable to disable the progress bar in onResponse() (the callback is created in onCreate()), when the fetch succeeds?
Fragment layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/source_items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<com.gc.materialdesign.views.ProgressBarCircularIndeterminate
android:id="#+id/progress_bar"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="#1E88E5"
android:layout_gravity="center_horizontal"/>
<ListView android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/source_list"/>
</FrameLayout>
onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mItemList = new ArrayList<SourceItem>();
RestClient.get().getSources().enqueue(new Callback<SourceItemList>() {
#Override
public void onResponse(Response<SourceItemList> response, Retrofit retrofit) {
if (response.isSuccess()) {
disableProgressBar();
//...
}
}
#Override
public void onFailure(Throwable t) {
disableProgressBar();
return;
}
private void disableProgressBar() {
if (mView != null) {
mView.findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
}
}
});
// ...
}
onCreateView():
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_sourceitem_list, viewGroup, false);
// ...
return mView;
}
Thanks
It is better to have progress bar initialized in onCreateView() rather than using view.findViewById() as it's an expensive operation.