I am developing a small app where users can post something and comment on a post. I am using the Instamaterial project on github as a reference.
There is a comment activity with animation while users post a new comment. Everything is working cool ... but when I post the very first comment... the app is crashing and not showing the comment animation (scroll to top) I spent the whole day trying ... but could not find the solution.
public class CommentsActivity extends AppCompatActivity implements SendCommentButton.OnSendClickListener { public static final String ARG_DRAWING_START_LOCATION = "arg_drawing_start_location"; LinearLayout contentRoot; RecyclerView rvComments; LinearLayout llAddComment; EditText etComment; SendCommentButton btnSendComment; private CommentsAdapter commentsAdapter; private int drawingStartLocation; Toolbar toolbar; private int currentPostID; DatabaseHelper db; private PreferencesManager preferencesManager; ArrayList<PostResponse.PostComments> comments; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_comments); toolbar = (Toolbar) findViewById(R.id.toolbar); contentRoot = (LinearLayout) findViewById(R.id.contentRoot); rvComments = (RecyclerView) findViewById(R.id.rvComments); llAddComment = (LinearLayout) findViewById(R.id.llAddComment); etComment = (EditText) findViewById(R.id.etComment); btnSendComment = (SendCommentButton) findViewById(R.id.btnSendComment); preferencesManager = new PreferencesManager(CommentsActivity.this); db = new DatabaseHelper(CommentsActivity.this); setupToolbar(); setupComments(); setupSendCommentButton(); drawingStartLocation = getIntent().getIntExtra(ARG_DRAWING_START_LOCATION, 0); if (savedInstanceState == null) { contentRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { contentRoot.getViewTreeObserver().removeOnPreDrawListener(this); startIntroAnimation(); return true; } }); } } protected void setupToolbar() { if (toolbar != null) { setSupportActionBar(toolbar); getSupportActionBar().setDisplayShow HomeEnabled(true); toolbar.setNavigationIcon(R.mipmap.ic_menu_cancel); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); } } private void setupComments() { LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); rvComments.setLayoutManager(linearLayoutManager); rvComments.setHasFixedSize(true); currentPostID = getIntent().getIntExtra(PostResponse.KEY_ID, -1); comments = db.getCommentsByPostID(currentPostID); commentsAdapter = new CommentsAdapter(this, comments); rvComments.setAdapter(commentsAdapter); rvComments.setOverScrollMode(View.OVER_SCROLL_NEVER); rvComments.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { commentsAdapter.setAnimationsLocked(true); } } }); } private void setupSendCommentButton() { btnSendComment.setOnSendClickListener(this); } private void startIntroAnimation() { ViewCompat.setElevation(getToolbar(), 0); contentRoot.setScaleY(0.1f); contentRoot.setPivotY(drawingStartLocation); llAddComment.setTranslationY(200); contentRoot.animate() .scaleY(1) .setDuration(200) .setInterpolator(new AccelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { ViewCompat.setElevation(getToolbar(), Utils.dpToPx(8)); animateContent(); } }) .start(); } private void animateContent() { commentsAdapter.updateItems(); llAddComment.animate().translationY(0) .setInterpolator(new DecelerateInterpolator()) .setDuration(200) .start(); } @Override public void onBackPressed() { ViewCompat.setElevation(getToolbar(), 0); contentRoot.postDelayed(new Runnable() { @Override public void run() { contentRoot.animate() .translationY(Utils.getScreenHeight(CommentsActivity.this)) .setDuration(200) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { CommentsActivity.super.onBackPressed(); overridePendingTransition(0, 0); } }) .start(); } }, 100); } @Override public void onSendClickListener(View v) { if (validateComment()) { publishComment(etComment.getText().toString()); PostResponse.PostComments newComment = new PostResponse().new PostComments(); newComment.user_id = Integer.parseInt(preferencesManager.getUserID()); newComment.comment = etComment.getText().toString(); newComment.post_id = currentPostID; commentsAdapter.addItem(newComment); commentsAdapter.setAnimationsLocked(false); commentsAdapter.setDelayEnterAnimation(true); rvComments.smoothScrollBy(0, rvComments.getChildAt(0).getHeight() * commentsAdapter.getItemCount()); etComment.setText(null); btnSendComment.setCurrentState(SendCommentButton.STATE_DONE); } } private boolean validateComment() { if (TextUtils.isEmpty(etComment.getText())) { btnSendComment.startAnimation(AnimationUtils.loadAnimation(this, R.anim.shake_error)); return false; } return true; } private Toolbar getToolbar(){ return toolbar; } /************************************************ * VOLLEY CALLS TO PUBLISH A COMMENT * *****************************************************/ private void publishComment(final String comment) { //Code here } } This is my adapter
public class CommentsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context; private int lastAnimatedPosition = -1; private int avatarSize; private int itemsCount = 0; private boolean animationsLocked = false; private boolean delayEnterAnimation = true; List<PostResponse.PostComments> mItems; public CommentsAdapter(Context context, List<PostResponse.PostComments> mItems) { this.context = context; this.mItems = mItems; avatarSize = context.getResources().getDimensionPixelSize(R.dimen.comment_avatar_size); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(context).inflate(R.layout.item_comment, parent, false); return new CommentViewHolder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { runEnterAnimation(viewHolder.itemView, position); CommentViewHolder holder = (CommentViewHolder) viewHolder; holder.tvComment.setText(mItems.get(position).comment); Picasso.with(context) .load(R.drawable.user_profile) .centerCrop() .resize(avatarSize, avatarSize) .transform(new RoundedTransformation()) .into(holder.ivUserAvatar); } private void runEnterAnimation(View view, int position) { if (animationsLocked) return; if (position > lastAnimatedPosition) { lastAnimatedPosition = position; view.setTranslationY(100); view.setAlpha(0.f); view.animate() .translationY(0).alpha(1.f) .setStartDelay(delayEnterAnimation ? 20 * (position) : 0) .setInterpolator(new DecelerateInterpolator(2.f)) .setDuration(300) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { animationsLocked = true; } }) .start(); } } @Override public int getItemCount() { return mItems.size(); } public void updateItems() { notifyDataSetChanged(); } public void addItem(PostResponse.PostComments newComment) { mItems.add(newComment); notifyItemInserted(mItems.size() - 1); } public void setAnimationsLocked(boolean animationsLocked) { this.animationsLocked = animationsLocked; } public void setDelayEnterAnimation(boolean delayEnterAnimation) { this.delayEnterAnimation = delayEnterAnimation; } public static class CommentViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.ivUserAvatar) ImageView ivUserAvatar; @Bind(R.id.tvComment) TextView tvComment; public CommentViewHolder(View view) { super(view); ButterKnife.bind(this, view); } } } Here is the screens.
The first works well but adding the first comment never works. I get this exception.
// Here is the exception
java.lang.NullPointerException at com.test.abc.CommentsActivity.onSendClickListener(CommentsActivity.java:214) at com.test.abc.views.SendCommentButton.onClick(SendCommentButton.java:80) at android.view.View.performClick(View.java:4463) at android.view.View$PerformClick.run(View.java:18770) at android.os.Handler.handleCallback(Handler.java:808) at android.os.Handler.dispatchMessage(Handler.java:103) at android.os.Looper.loop(Looper.java:193) 1 Answers
Answers 1
I know you know it is because of this line -(you told me in comments)
rvComments.smoothScrollBy(0, rvComments.getChildAt(0).getHeight() * commentsAdapter.getItemCount()); but did you know it is the method call? rvComments.getChildAt(0).getHeight() ?
my thoughts is, if the recyleView is empty calling rvComments.getChildAt(0) is a reference to Null Reference, its empty hence you NPE but if the first comment is already added you have list of size 1 so this line rvComments.getChildAt(0).getHeight() actaully reference a View Object address.
So i guess your solution is _since you know the preferred height of it View or pick a preferred height and hardcode it and use. Or if you really insist of using the View height then check the list size before you advance.
... commentsAdapter.setAnimationsLocked(false); commentsAdapter.setDelayEnterAnimation(true); if(rvComments.getChildCount() > 0){ //i guess you will have do rvComments.getChildAt( //rvComments.getChildCount()-1) rather than below rvComments.smoothScrollBy(0, rvComments.getChildAt(0).getHeight() * commentsAdapter.getItemCount()); }else{ //do something else or find something to do to get the height you want //and multiply. In short it means the first item. ...
0 comments:
Post a Comment