I am working on an application for tracking calories. I have a SQLite Database implemented with Room and a RecyclerView in on of my Fragments which shows the saved food of the users.
I have problems with the performance of the list. With about 50 entries it takes at least one second in the emulator. On my mobile phone (Samsung Galaxy S5) it already takes several seconds. A list element doesn't show much information, so I wonder about the speed.
I have already read many entries on the Internet, but have not become smarter. I load the pictures with Glide, otherwise only 2 TextViews are shown.
I also wondered if the problem is notifyDataSetChanged(). An alternative would be DiffUtil. I already implemented AsyncListDiffer but the RecyclerView is still pretty slow.
This is the important part of my Fragment:
if(!mAdapter.hasObservers()) mAdapter.setHasStableIds(true);
mAdapter.setOnItemClickListener(new FoodRecyclerViewAdapter.OnItemClickListener() {
#Override
public void onItemClick(Food food) {
DiaryEntriesAddDialog dialog = DiaryEntriesAddDialog.newInstance(food);
dialog.show(getActivity().getSupportFragmentManager(), DiaryEntriesAddDialog.class.getName());
}
});
mRecyclerViewFood.setAdapter(mAdapter);
mRecyclerViewFood.setLayoutManager(new LinearLayoutManager(getContext());
mRecyclerViewFood.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
mFoodViewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
mFoodViewModel.getAll().observe(this, new Observer<List<Food>>() {
#Override
public void onChanged(#Nullable final List<Food> foods) {
mFoods = foods;
mAdapter.setItems(foods);
});
The function I update my list:
public void setItems(List<Food> foods) {
mFoods = foods;
notifyDataSetChanged();
}
And this is the ViewHolder in my Adapter:
public class ViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.recyclerview_foodlist_item_constraintlayout) ConstraintLayout mConstraintLayout;
#BindView(R.id.recyclerview_foodlist_item_name) TextView mTextViewName;
#BindView(R.id.recyclerview_foodlist_item_unit) TextView mTextViewUnit;
#BindView(R.id.recyclerview_foodlist_item_imageview_type) ImageView mImageViewType;
#BindArray(R.array.array_dialog_foodadd_units) String[] mUnitNames;
public ViewHolder(#NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void bind(final int position) {
final Food food = getItem(position);
mTextViewName.setText(food.getName());
mTextViewUnit.setText(mUnitNames[food.getUnit()]);
Glide.with(mContext).load(getEntryTypeDrawableResource(food)).into(mImageViewType);
}
Does anyone have an explanation? Am I doing something fundamentally wrong?
EDIT
I did some further investigations. According to the Profiler the LinearLayoutManager.layoutChunk is the most time consuming operation (in debug: ~2 sec) while loading/displaying the items. Each onCreateViewHolder() needs between 3 ms and 100 ms. Almost 100 items could then easily generate these 2 sec.
But why is it that long?
This is my onCreateViewHolder()
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
mContext = viewGroup.getContext();
mLayoutInflater = LayoutInflater.from(mContext);
View view = mLayoutInflater.inflate(R.layout.recyclerview_foodlist_item, viewGroup, false);
return new ViewHolder(view);
}
And this is the layout I am inflating
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
android:id = "#+id/recyclerview_foodlist_item_constraintlayout"
android:layout_width = "match_parent"
android:layout_height = "56dp"
android:background = "?android:attr/selectableItemBackground"
android:clickable = "true"
android:focusable = "true"
android:foreground = "?android:attr/selectableItemBackground"
>
<ImageView
android:id = "#+id/recyclerview_foodlist_item_imageview_type"
android:layout_width = "24dp"
android:layout_height = "24dp"
android:layout_marginStart = "16dp"
android:layout_marginTop = "16dp"
android:layout_marginBottom = "16dp"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintStart_toStartOf = "parent"
app:layout_constraintTop_toTopOf = "parent"
/>
<TextView
android:id="#+id/recyclerview_foodlist_item_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop = "16dp"
android:layout_marginEnd = "16dp"
android:layout_marginBottom = "16dp"
android:text="Name"
android:textAppearance = "#style/TextAppearance.AppCompat.Menu"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toStartOf = "#+id/recyclerview_foodlist_item_unit"
app:layout_constraintStart_toEndOf = "#+id/recyclerview_foodlist_item_imageview_type"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/recyclerview_foodlist_item_unit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop = "16dp"
android:layout_marginEnd = "16dp"
android:layout_marginBottom = "16dp"
android:text="Unit"
android:textAppearance = "#style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf = "parent"
/>
</android.support.constraint.ConstraintLayout>
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 2 years ago.
I have a button placed inside a layout frag_contacts.xml that is intended for a fragment which is FragmentContacts.java . There is also row layout for the recyclerview which is items_contacts.xml . The button allows a user to save stuff after they are done picking recyclerview items. The app works without any errors on android Nougat .
However When I run the app on android 9 it crushes giving exception java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference.
The line throwing exceptions is the one below
buttonCreateGroup.setOnClickListener(this);
the error message
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.ex_contactapp, PID: 3931
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.example.ex_contactapp.fragments.FragmentContacts.onCreateView(FragmentContacts.java:99)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2439)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at androidx.fragment.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2243)
at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:654)
at androidx.fragment.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:146)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1244)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1092)
at androidx.viewpager.widget.ViewPager.onMeasure(ViewPager.java:1622)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
at android.view.View.measure(View.java:25056)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7745)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:1051)
at android.view.View.measure(View.java:25056)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3382)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2104)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2403)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1964)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8721)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:988)
at android.view.Choreographer.doCallbacks(Choreographer.java:765)
at android.view.Choreographer.doFrame(Choreographer.java:700)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:967)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7285)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
layout for fragment - frag_contacts.xml
<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="#color/colorPrimaryDark">
<GridLayout
android:layout_width="wrap_content"
android:layout_height="100dp">
<EditText
android:id="#+id/group_name"
android:layout_width="263dp"
android:layout_height="70dp"/>
<Button
android:id="#+id/buttonCreateGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/create_group"/>
</GridLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_contacts"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
Row layout -> items_contacts.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:layout_margin="10dp"
android:weightSum="10">
<Button
android:layout_gravity="center"
android:background="#drawable/ic_person"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="32dp"/>
<LinearLayout
android:padding="10dp"
android:layout_width="0dp"
android:layout_weight="8"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="#+id/contact_name"
android:text="name"
android:textSize="20sp"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/contact_number"
android:text="number"
android:textSize="16sp"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<CheckBox
android:id="#+id/contact_checkbox"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_height="32dp"
android:layout_weight="1"/>
</LinearLayout>
Recycler View Adapter class -ContactsRvAdapter.java
public class ContactsRvAdapter extends RecyclerView.Adapter<ContactsRvAdapter.ViewHolder> {
private Context mContext;
private LayoutInflater inflater;
private List<ModelContacts> mlistContacts;
private CheckedStatusListener mcheckedStatusListener;
public interface CheckedStatusListener{
//String firstName, String lastName, String middleName, #NonNull String phoneNumber,#NonNull int groupid
void onItemChecked(String name, String phonenumber);
void onItemUnchecked(String name, String phonenumber);
}
public ContactsRvAdapter(Context context, List<ModelContacts> listContacts,CheckedStatusListener checkedStatusListener){
mlistContacts = listContacts;
mContext = context;
mcheckedStatusListener = checkedStatusListener;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.items_contacts,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
final TextView contact_name,contact_number;
final CheckBox contact_checkbox;
contact_name = holder.contact_name;
contact_number = holder.contact_number;
contact_name.setText(mlistContacts.get(position).getName());
contact_number.setText(mlistContacts.get(position).getNumber());
contact_checkbox = holder.contactSelectedCheckBox;
contact_checkbox.setOnClickListener(v -> {
if (contact_checkbox.isChecked()){
mcheckedStatusListener.onItemChecked(mlistContacts.get(position).getName(),mlistContacts.get(position).getNumber());
}else{
mcheckedStatusListener.onItemUnchecked(mlistContacts.get(position).getName(),mlistContacts.get(position).getNumber());
}
});
}
#Override
public int getItemCount() {
return mlistContacts.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView contact_name,contact_number;
CheckBox contactSelectedCheckBox;
public ViewHolder(View itemView){
super(itemView);
contact_name = itemView.findViewById(R.id.contact_name);
contact_number = itemView.findViewById(R.id.contact_number);
contactSelectedCheckBox = itemView.findViewById(R.id.contact_checkbox);
}
}
}
Fragment class - FragmentContacts.java
public class FragmentContacts extends Fragment implements ContactsRvAdapter.CheckedStatusListener, View.OnClickListener{
private View v;
private RecyclerView recyclerView;
private List<ModelContactsBuffer> currentSelectedContacts = new ArrayList<>();
ContactsRvAdapter adapter;
private EditText editTextGroupName;
private ContactGroupViewModel contactGroupViewModel;
private ModelContactsBuffer modelContactsBuffer;
private GroupListViewModel groupListViewModel;
public FragmentContacts() {
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
v = inflater.inflate(R.layout.frag_contacts,container,false);
recyclerView = v.findViewById(R.id.rv_contacts);
editTextGroupName = v.findViewById(R.id.group_name);
Button buttonCreateGroup = v.findViewById(R.id.buttonCreateGroup);
buttonCreateGroup.setOnClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
RecyclerView.LayoutManager layoutManager= linearLayoutManager;
recyclerView.setLayoutManager(layoutManager);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M &&
ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.READ_CONTACTS},1);
}else {
adapter = new ContactsRvAdapter(getContext(), getContacts(),this);
recyclerView.setItemViewCacheSize(getContacts().size());
recyclerView.setAdapter(adapter);
// adapter.notifyDataSetChanged();
}
contactGroupViewModel = ViewModelProviders.of(this, new ContactGroupViewModel.Factory(Objects.requireNonNull(getActivity()).getApplicationContext())).get(ContactGroupViewModel.class);
groupListViewModel = ViewModelProviders.of(this,new GroupListViewModel.Factory(getActivity().getApplicationContext())).get(GroupListViewModel.class);
return v;
}
#Override
public void onClick(View v) {
Log.i("editTextGroupNamelength", String.valueOf(editTextGroupName.getText().toString().length()));
if (editTextGroupName.getText().toString().length() > 2) {
if(currentSelectedContacts.size() > 4){
contactGroupViewModel.createGroup(editTextGroupName.getText().toString(),String.valueOf(currentSelectedContacts.size()));
Integer groupId = contactGroupViewModel.readGroupId(editTextGroupName.getText().toString());
insertGrouplist(groupId);
//clearFields();
}else{
new AlertDialog.Builder(Objects.requireNonNull(this.getActivity()))
.setIcon(R.drawable.ic_error)
.setTitle("Insufficient group members")
.setMessage("A group should have at least 5 members")
.setNeutralButton("Ok",null)
.show();
}
}else{
new AlertDialog.Builder(Objects.requireNonNull(this.getActivity()))
.setIcon(R.drawable.ic_error)
.setTitle("Group name is too short")
.setMessage("A group should have atleast be 3 characters long")
.setNeutralButton("Ok",null)
.show();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if(requestCode == 1){
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
//now permission is granted call function again
adapter = new ContactsRvAdapter(getContext(), getContacts(),this);
recyclerView.setItemViewCacheSize(getContacts().size());
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
}
private List<ModelContacts> getContacts(){
List<ModelContacts> list = new ArrayList<>();
try{
String[] PROJECTION = new String[] {
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Photo.CONTACT_ID };
String selectionFields = ""+ ContactsContract.Contacts.HAS_PHONE_NUMBER + " > 0 and " + ContactsContract.CommonDataKinds.Phone.TYPE +"=" + ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE;
String[] selectionArgs = new String[]{"com.google"};
Cursor cursor = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
PROJECTION,selectionFields,null,ContactsContract.Contacts.DISPLAY_NAME + " ASC");
cursor.moveToFirst();
while(cursor.moveToNext()){
list.add(new ModelContacts(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID)),
cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))));
}
}catch (Exception e){
Toast.makeText(getContext(),e.getMessage(),Toast.LENGTH_SHORT).show();
}
return list;
}
#Override
public void onItemChecked(String name, String phonenumber) {
String firstName = " ";
String lastName = " ";
if(name.split("\\w+").length>1){
lastName = name.substring(name.lastIndexOf(" ")+1);
firstName = name.substring(0,name.lastIndexOf(' '));
}else{
firstName = name;
lastName = " ";
}
modelContactsBuffer = new ModelContactsBuffer(firstName,lastName,phonenumber);
currentSelectedContacts.add(modelContactsBuffer);
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onItemUnchecked(String name, String phonenumber) {
String firstName = " ";
String lastName = " ";
if(name.split("\\w+").length>1){
lastName = name.substring(name.lastIndexOf(" ")+1);
firstName = name.substring(0,name.lastIndexOf(' '));
}else{
firstName = name;
lastName = " ";
}
currentSelectedContacts.removeIf( currentSelectedContact -> currentSelectedContact.getPhoneNumber().equals(phonenumber));
}
public void insertGrouplist(int groupId){
for (ModelContactsBuffer currentSelectedContact : currentSelectedContacts) {
groupListViewModel.createGroupList(currentSelectedContact.getFirstName(), currentSelectedContact.getLastName(), " ", currentSelectedContact.getPhoneNumber(), groupId);
}
clearFields();
}
}
I commented the line throwing an exception and the app runs pretty well. Hence the problem is from buttonCreateGroup.setOnClickListener(this);
I have tried many solutions here in stack overflow which didnt work.
I have checked the id of the button and it is correct
I don't think it is possible initializing the onclicklistener inside ContactsRvAdapter because its not even in the row layout. It is in the other layout wwhich is intended for the fragment
I tried passing the normal new OnViewClicklistener to the button instead of passing the whole activity but nothong changed. Still getting an error
I later on moved the clicklistener call from public View onCreateView() to public void onViewCreated() but there was no change. I am stil getting the same error.
I tried removing #RequiresApi(api = Build.VERSION_CODES.N) Nothing changed
Kindly note I am not a professional on android. I added some things like #RequiresApi(api = Build.VERSION_CODES.N) because android lint would show an error and that was the only way I could deal with it at that time. I apprectiate any help offered on solving this
You probably get this error(null object reference) because you didn't define the button. You have to define like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_contacts, container, false);
Button buttonCreateGroup = view.findViewById(R.id.buttonCreateGroup);
buttonCreateGroup.setOnClickListener(this);
return view;
}
Or this func:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button buttonCreateGroup = view.findViewById(R.id.buttonCreateGroup);
buttonCreateGroup.setOnClickListener(this);
}
edit func.
So for some apparent reason I didn't think about the fact that I had two xml files for frag_contacts, one in res/layouts and another in res/layout-v26
I admit I am not a pro on android and I kinda auto generated the latter xml file without knowing its use but I still continued working on the on in res/layouts & fully abandoned the one in res/layout-v26 because I didn't know it's use.
However it did have a button and a couple of other widgets but with no ID. Since it was in res/layout-v26 meaning it would show for devices compatible with v-26 and totally ignore the one on res/layouts, that's the layout file that would show on android 9 device. & since the button didn't have an ID, a null pointer exception would be thrown.
I have deleted the layout-v-26 file so that devices will fallback to the default layout. The app now works fine.
I am working on a social network application in which I need to show feeds to subscribers.. for showing feeds in a list. I am using RecyclerView. No Problem till now... but the problem comes when I have to show some comments on the feed. I am trying to accomplish a layout similar to Instagram where they show an image and maximum 3-4 comments with it in the main list. I am getting Image links and its comments (in JSON array form) from server.. sometimes I get comment array size 0 and sometime 1/2../10. I have created an XML for the comment layout that contains 2 TextView and I am inflating it in FeedViewHolder based on my ViewType.. my item_comment look like
<RelativeLayout
android:id="#+id/comment_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingTop="5dp">
<TextView
android:textStyle="bold"
android:textColor="#color/indigo_500"
android:id="#+id/comment_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="#drawable/profile_pic"
android:text="Somesh Kumar"/>
<TextView
android:id="#+id/comment_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="#+id/comment_name"
android:ellipsize="end"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="This is a comment"/>
</RelativeLayout>
I am passing my feed list to RecyclerView adaptor..and then I am checking if the item (that will be shown) should have the comments or not.
#Override
public int getItemViewType(int position)
{
FeedParser.DataClass dataClass = feedList.get(position);
int ITEM_TYPE = 0;
if (dataClass.getPost_type() == FEED_TYPE_PICTURE) // Feed item contains caption with pictures
{
// check if it has comments
if (dataClass.getComments().size() == 0)
{
ITEM_TYPE = PICTURE_WITHOUT_COMMENTS;
}
else
{
ITEM_TYPE = PICTURE_WITH_COMMENTS;
}
}
return ITEM_TYPE;
}
and passing ITEM_TYPE to onCreateViewHolder like
#Override
public FeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_feed, parent, false);
return new FeedViewHolder(itemView, viewType);
}
and in FeedViewHolder inflating item layout and comment layout if viewType has comments
public FeedViewHolder(View itemView, int viewType)
{
super(itemView);
tvFeedUsername = (TextView) itemView.findViewById(R.id.tvFeedUsername);
tvFeedCaption = (TextView) itemView.findViewById(R.id.tvFeedCaption);
switch (viewType)
{
// TODO: Write code for other feed type such as picture and video
// viewType 1 = PICTURE_WITH_COMMENTS
case 1:
layoutComments = (LinearLayout) itemView.findViewById(R.id.layoutComments);
commentView = layoutInflater.inflate(R.layout.item_comment, null);
postCommentText = (TextView) commentView.findViewById(R.id.comment_text);
postCommentName = (TextView) commentView.findViewById(R.id.comment_name);
layoutComments.addView(commentView);
break;
}
}
I have verified this works perfect but when i setText for holder.postCommentName & holder.postCommentText in onBindViewHolder it only set text for last comment ( i know it's because i am looping through all the comment and setting them one by one on the same holder.postCommentText and holder.postCommentName here is my onBindViewHolder..
#Override
public void onBindViewHolder(FeedViewHolder holder, int position)
{
FeedParser.DataClass feedModel = feedList.get(position);
holder.tvFeedUsername.setText(feedModel.getName());
holder.tvFeedCaption.setText(feedModel.getPost_status());
if (getItemViewType(position) == TEXT_WITH_COMMENTS)
{
for (int commentNum = 0; commentNum < feedModel.getComments().size(); commentNum++)
{
holder.postCommentName.setText(feedModel.getComments().get(commentNum).getUser_name());
holder.postCommentText.setText(feedModel.getComments().get(commentNum).getComment());
}
}
}
I don't know even if this is the right approach or not... but this is how some people wrote an answer here... like This one . I have searched many SO post but not getting what I want. Is there any other way where I can inflate comment's XML layout and add it multiple time on RecyclerView 's item?
Thanks any help would be much appreciated!
i Believe you need to create a nested recycler view in the main recycler view and you need to pass the data into the nested adapter as that of the comments .So basically
1.)Main Recycler View contains Caption , Image , RecyclerView for comments (as per instagram)
2.)In the second reyclerview put your above comment layout and create a seperate adapter with data as comments
Thats It .. Ask me for any further help
I'm building my first app based on material from http://javatechig.com/video/json-feed-reader-in-android.
Everything goes ok so far, but I found one bug with ListView elements, which I can not manage to resolve by myself :(
I have extended list_row_layout.xml by 2 fields:
<Button
android:layout_width="wrap_content"
android:layout_height="20dp"
android:text="komcie"
android:textSize="11sp"
android:id="#+id/loadComments"
android:layout_gravity="center|bottom"
android:background="#bbb"
android:layout_marginLeft="5dp"
android:enabled="true"
android:clickable="true"
android:onClick="clickedLoadComments"
android:elegantTextHeight="true"
android:layout_toRightOf="#id/thumbImage"
android:layout_below="#+id/content"
android:padding="1px" />
<ListView
android:id="#+id/comment_list"
android:layout_toRightOf="#id/thumbImage"
android:layout_below="#+id/content"
android:paddingTop="5dp"
android:layout_marginTop="0dp"
android:paddingLeft="5dp"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:cacheColorHint="#00000000"
android:dividerHeight="1dp"
android:focusable="false"
android:listSelector="#drawable/list_selector_flatcolor"
android:visibility="invisible" />
Button.android:onClick="clickedLoadComments" function load Json with elements into ListView/comment_list. It works quite fine. But if there are more elements than could be displayed on screen (~8 elements) there is a bug. Comments from clicked element are loaded into every 8th element in a ListView.
Some code:
public void clickedLoadComments(View v)
{
try {
View parent = (View)v.getParent();
ViewHolder t = (ViewHolder) parent.getTag();
if( parent != null ) {
this.loadCommentsForLeaf(parent);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
protected void loadCommentsForLeaf( View view )
{
String tmpUrl = "http://some.url.com/Ajax/LoadComments?lid=" + this.currentLeafInUse;
JSONObject commentsJson = this.getJSONFromUrl(tmpUrl);
this.parseJsonComments(commentsJson);
if( commentsJson != null )
this.updateCommentList(view);
}
public void updateCommentList( View view) {
commentListView = (ListView) view.findViewById(R.id.comment_list);
commentListView.setVisibility(View.VISIBLE);
CommentListAdapter cla = new CommentListAdapter(this, this.commentList.get(this.currentLeafInUse));
commentListView.setAdapter(cla);
// Set list height.
ViewGroup.LayoutParams params = commentListView.getLayoutParams();
params.height = setListViewHeightBasedOnItems(commentListView) + 20;
commentListView.setLayoutParams(params);
commentListView.requestLayout();
}
CustomListAdapter.java code is mostly the same as the one in tutorial.
I would really appreciate help as I have spent many hours figuring it out with not success :(
This is just a guess. You might post your Adapter code and your parseJsonComments also if this does not work.
The Cause:
The problem you are describing might be caused due to the recycling and the reusage of Views. Take a look at this image from http://android.amberfog.com
As you can see the 1. items is reused and becomes the 8. item after scrolling.
Let's assume that Item 1 has an OnClickListener which updates a Text of the item.
For example we set the text to "clicked" after the OnClickListener is triggered.
Because item 1 is reused to create item 8, item 8 will also display the text "clicked".
The Solution:
The usual way is to save all states/content in a List(or whatever) and update everything in the getView call. So if you want to update text:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
...
holder.textView.setText(jsonTexts[position]);
...
return convertView;
}
And if you want to update an item just update the List in your Adapter which holds the content/JsonObjects(etc.) and call notifyDataSetChanged.
public void updateCommentList(JSONObject commentsJson, int position) {
// does not exist you might create something
//like that in your Adapter class
commentListAdapter.updateItem(commentsJson,position);
commentListAdapter.notifyDataSetChanged();
}
After i populate the listview i call this method:
private void registerClickCallback() {
ListView list = (ListView) findViewById(R.id.lv);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View viewClicked,
int position, long id) {
String xx = position+ ":" + id;
//then you can do what ever you want
}
});
}
I'm fairly new to android dev and am trying to learn ropes. In order to do that I've been messing around Custom Views in android. I am trying to build an alarm clock app and I want to make a nice spinner to select the times from. Similar to say this:
http://i47.tinypic.com/aymyjc.jpg
I have created an AndroidScollSpinner class that looks like this:
public class AndroidScrollSpinner extends View {
public AndroidScrollSpinner(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawSomeText(canvas);
canvas.restore();
}
private void drawSomeText(Canvas canvas) {
Paint titlePaint = new Paint();
titlePaint.setColor(Color.BLUE);
canvas.drawTextOnPath("Bert", new Path(), 0.0f,0.0f, titlePaint);
}
private void drawBackground(Canvas canvas) {
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(getResources().getColor(R.color.bluegrass));
backgroundPaint.setStyle(Paint.Style.FILL);
Bitmap background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
canvas.drawBitmap(background, 0, 0, backgroundPaint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec));
int h = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(w, h);
}
}
The issue I am having is in the onMeasure
The MeasureSpec.getSize(widthMeasureSpec) always returns 0. Does anyone know why? Or what I am missing here?
Here's my layout file as well.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:id="#+id/addAlarmSpinnerLayout">
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<ToggleButton android:id="#+id/sundayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/S"
android:textOff="#string/S"/>
<ToggleButton android:id="#+id/mondayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/M"
android:textOff="#string/M"/>
<ToggleButton android:id="#+id/tuesdayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/T"
android:textOff="#string/T"/>
<ToggleButton android:id="#+id/wednesdayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/W"
android:textOff="#string/W"/>
<ToggleButton android:id="#+id/thursdayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/T"
android:textOff="#string/T"/>
<ToggleButton android:id="#+id/fridayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/F"
android:textOff="#string/F"/>
<ToggleButton android:id="#+id/saturdayToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.14"
android:textOn="#string/S"
android:textOff="#string/S"/>
</LinearLayout>
<Button android:id="#+id/doneButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/done"
android:onClick="onDoneClicked">
</Button>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hour24Clock"
android:id="#+id/hour24switch"
android:layout_gravity="center"
android:enabled="true"
android:onClick="createSpinners"/>
</LinearLayout>
If you need to know anything else about the app, have a look here under https://github.com/rdsmallwood928/NeverLate
Also I do realize that there probably third party libraries out there that I can use to get the spinner effect that I want. However, I'm really doing this as a learning exercise so it more important to me to understand why this code always returns 0 than to inject a custom component from somewhere else and get on with my life. Thanks in advance for your help!
EDIT: Here is the AddAlarmFragment class that creates the spinners
public class AddAlarmFragment extends Fragment {
private AndroidClickSpinner minuteSpinner;
private AndroidClickSpinner hourSpinner;
private AndroidClickSpinner amPmSpinner;
private ToggleButton mondayToggle;
private ToggleButton tuesdayToggle;
private ToggleButton wednesdayToggle;
private ToggleButton thursdayToggle;
private ToggleButton fridayToggle;
private ToggleButton saturdayToggle;
private ToggleButton sundayToggle;
private Switch hour24Switch = null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.add_alarm, container, false);
}
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
createSpinners(view);
}
#Override
public void onResume() {
super.onResume();
}
public void createSpinners(View view) {
LinearLayout layout = (LinearLayout) view.findViewById(R.id.addAlarmSpinnerLayout);
hour24Switch = (Switch) view.findViewById(R.id.hour24switch);
layout.removeAllViews();
ArrayList<Object> hours = new ArrayList<>();
if(hour24Switch.isChecked()) {
for(int i=0;i<24;i++) hours.add(i);
} else {
for(int i=1;i<=12;i++) hours.add(i);
}
hourSpinner = new AndroidClickSpinner(getActivity(), hours);
layout.addView(hourSpinner);
ArrayList<Object> minutes = new ArrayList<>();
for(int i=0;i<=60;i++) minutes.add(i);
minuteSpinner = new AndroidClickSpinner(getActivity(), minutes);
layout.addView(minuteSpinner);
if(!hour24Switch.isChecked()) {
ArrayList<Object> amPm = new ArrayList<>();
amPm.add("AM");
amPm.add("PM");
amPmSpinner = new AndroidClickSpinner(getActivity(), amPm);
layout.addView(amPmSpinner);
}
mondayToggle = (ToggleButton) view.findViewById(R.id.mondayToggleButton);
tuesdayToggle = (ToggleButton) view.findViewById(R.id.tuesdayToggleButton);
wednesdayToggle = (ToggleButton) view.findViewById(R.id.wednesdayToggleButton);
thursdayToggle = (ToggleButton) view.findViewById(R.id.thursdayToggleButton);
fridayToggle = (ToggleButton) view.findViewById(R.id.fridayToggleButton);
saturdayToggle = (ToggleButton) view.findViewById(R.id.saturdayToggleButton);
sundayToggle = (ToggleButton) view.findViewById(R.id.sundayToggleButton);
//Prevent no day selected...
switch (new LocalDate().getDayOfWeek()) {
case 1:
mondayToggle.setSelected(true);
break;
case 2:
tuesdayToggle.setSelected(true);
break;
case 3:
wednesdayToggle.setSelected(true);
break;
case 4:
thursdayToggle.setSelected(true);
break;
case 5:
fridayToggle.setSelected(true);
break;
case 6:
saturdayToggle.setSelected(true);
break;
case 7:
sundayToggle.setSelected(true);
break;
}
PieChart pie = new PieChart(getActivity());
Resources res = getResources();
pie.addItem("Agamemnon", 2, res.getColor(R.color.seafoam));
pie.addItem("Bocephus", 3.5f, res.getColor(R.color.chartreuse));
pie.addItem("Calliope", 2.5f, res.getColor(R.color.emerald));
pie.addItem("Daedalus", 3, res.getColor(R.color.bluegrass));
pie.addItem("Euripides", 1, res.getColor(R.color.turquoise));
pie.addItem("Ganymede", 3, res.getColor(R.color.slate));
layout.addView(pie);
layout.addView(new AndroidScrollSpinner(getActivity()));
}
public Integer getHours() {
Integer hour = Integer.parseInt(hourSpinner.getSelectedItem().toString());
if(!hour24Switch.isChecked()) {
if(hour == 12) {
hour = 0;
}
if(amPmSpinner.getSelectedItem().equals("PM")) {
hour = hour + 12;
}
}
return hour;
}
public Integer getMinutes() {
return Integer.parseInt(minuteSpinner.getSelectedItem().toString());
}
public boolean[] getDays() {
boolean[] days = new boolean[7];
days[0] = mondayToggle.isChecked();
days[1] = tuesdayToggle.isChecked();
days[2] = wednesdayToggle.isChecked();
days[3] = thursdayToggle.isChecked();
days[4] = fridayToggle.isChecked();
days[5] = saturdayToggle.isChecked();
days[6] = sundayToggle.isChecked();
return days;
}
}
I'm sorry, but where in your layout you are using it ?
Are you adding it from code, since I couldn't see it included somewhere in your XML layout file
I need to create Collapse / Expand forms in Android. I am thinking about using either RelativeLayout or TableLayout for this purpose. But, what XML element make these forms expand and hide in android?
If you are not sure what I am not talking about, take an application like Sales Force for an example. There you have these expandable menus in all the forms. How can I do this?
Following is an example (taken from Sales Force)
When you expand these, it looks like below
You could do the following. create a layout that has the following:
1. A Heading or a textview with the label contacts
2. Below it a layout that has forms related to it
3. Add another textview below #2 and name it address
4. Add a lyout below #3 .
The layout 2 and 4 will have visibility gone in the first case
When the user taps on layout 1, or the first textview, make layout 2 visible and vice versa. Do the same with the second textview.
Hope that helps.!
I have had a similar problem, i want parts of my form to be hidden in sektions and created a class for this issue.
public class section extends LinearLayout{
public LinearLayout container;
public Button toggler;
public section(Context context, String section_name, String section_state) {
super(context);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.flxsection, this);
container = (LinearLayout) this.findViewById(R.id.container);
container.setVisibility(section_state.equals("0") ? View.GONE:View.VISIBLE);
toggler = ((Button)this.findViewById(R.id.section_toggle));
toggler.setTag(section_state);
toggler.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
String tag = (String) v.getTag();
v.setTag(tag.equals("0") ? "1":"0");
if(tag.equals("0")){expand(container,false);}else{collapse(container,false);}
setImage(tag.equals("0"));
}
});
toggler.setText(" " + section_name);
setImage(section_state.equals("1"));
setTextSize();
}
public void setTextSize(){
toggler.setTextSize(GV.Style.TextSize);
}
public void setImage(boolean open){
int a = open ? R.drawable.minus_48_white: R.drawable.plus_48_white;
Drawable img = main.res.getDrawable(a);
final float scale = main.res.getDisplayMetrics().density;
int size = (int) (12 * scale + 0.5f);
img.setBounds(0,0,size,size);
toggler.setCompoundDrawables(img,null,null,null);
}
}
the xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:focusable="false"
android:layout_marginLeft="4dip"
android:layout_marginRight="4dip"
android:orientation="vertical" >
<Button
android:id="#+id/section_toggle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dip"
android:layout_marginTop="4dip"
android:background="#drawable/section"
android:drawableLeft="#drawable/plus_48"
android:focusable="false"
android:gravity="left|center_vertical"
android:padding="6dip"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:textSize="22dip" />
<LinearLayout
android:id="#+id/container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:background="#android:color/transparent"
android:orientation="vertical" >
</LinearLayout>
Expand and collapse:
public static void expand(final View v,boolean quick) {
v.requestLayout();
v.measure(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
final int targtetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.getLayoutParams().width = LayoutParams.FILL_PARENT;
v.setVisibility(View.VISIBLE);
if(quick){
v.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
v.requestLayout();
}else{
android.view.animation.Animation a = new android.view.animation.Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? LayoutParams.WRAP_CONTENT
: (int)(targtetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
int duration = (int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density);
if(duration> 500)duration=500;
a.setDuration(duration);
//(int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density)
v.startAnimation(a);
}
}
public static void collapse(final View v,boolean quick) {
v.requestLayout();
final int initialHeight = v.getMeasuredHeight();
if(quick){
v.setVisibility(View.GONE);
v.requestLayout();
}else{
android.view.animation.Animation a = new android.view.animation.Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 1){
v.setVisibility(View.GONE);
}else{
v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
v.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
int duration = (int)( initialHeight/ v.getContext().getResources().getDisplayMetrics().density);
if(duration> 500)duration=500;
a.setDuration(duration);
v.startAnimation(a);
}
}
If if create a form and need a section, i create a instance of this class and add the controls.
You might need to turn the hardware acceleration on in order to get the best performance
edit:
Usage is like:
section s = new section(context, section_name, section_state);
s.container.addView([your view 1]);
s.container.addView([your view 2]);
s.container.addView([your view 3]);
//...
form.addView(s);