2
\$\begingroup\$

I'm an iOS developer new to Android. I'm creating my first Android app and I have several questions. My app uses Android Studio, gradle, and it's based on Parse (the backend). Basically, takes messages from the backend and shows them. User has the possibility to delete them, or click them and see the detail, and of course unread messages have a different color.

This is the code of one class, called MessagesListFragment.java, after that a few questions.

package com.ricardoruizlopez.xxxxx.messages;

import ...

public class MessagesListFragment extends Fragment  {

private static final String LOG_TAG = "MessagesListFragment";
MessagesAdapter mAdapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View v = inflater.inflate(R.layout.messages_list_layout, container, false);
    final ListView listView = (ListView) v.findViewById(R.id.list);

    // load messages and attach them to the list
    ParseQuery<Message> messagesQuery= ParseQuery.getQuery(Message.class);
    messagesQuery.whereEqualTo("receiver", ParseUser.getCurrentUser());
    messagesQuery.whereEqualTo("deleted", false);
    messagesQuery.include("sender");
    messagesQuery.orderByDescending("createdAt");
    messagesQuery.setLimit(1000);
    final ProgressDialog progress = ProgressDialog.show(getActivity(), null, getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_LOADING_MESSAGES));
    messagesQuery.findInBackground(new FindCallback<Message>() {
        @Override
        public void done(List<Message> list, ParseException e) {
            progress.dismiss();
            if (e == null) {
                mAdapter = new MessagesAdapter(list);
                listView.setAdapter(mAdapter);

            } else {
                Log.d(LOG_TAG, "Error loading messages. " + e.getLocalizedMessage());
                new AlertDialog.Builder(getActivity())
                        .setTitle(R.string.ALERT_VIEW_ERROR)
                        .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_ERROR_LOADING_MESSAGES)
                        .setPositiveButton(R.string.ALERT_VIEW_OK, null)
                        .show();
            }
        }
    });

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Log.d(LOG_TAG, "onItemClick");
            Message message = (Message)mAdapter.getItem(position);

            // mark message as read if needed
            if (message.getUnread()) {
                message.setUnread(false);
                mAdapter.notifyDataSetChanged();
                message.saveInBackground();
            }

            // show message detail
            Intent messageDetail = new Intent(getActivity(), MessageDetailActivity.class);
            MessageDetailActivity.message = message;
            startActivity(messageDetail);
        }
    });

    return v;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    //requestMessages();
}

class MessagesAdapter extends BaseAdapter {

    private final List<Message> mMessages;

    public MessagesAdapter(List<Message> messages) {
        mMessages=messages;
    }

    @Override
    public int getCount() {
        return mMessages.size();
    }

    @Override
    public Object getItem(int position) {
        return mMessages.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = View.inflate(getActivity(), R.layout.message_item, null);
        }

        Message message=mMessages.get(position);
        CustomTextView senderTextView = (CustomTextView) convertView.findViewById(R.id.senderTextView);
        senderTextView.setText(getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_MESSAGE_FROM, message.getSender().getString("displayName")));
        CustomTextView dateTextView = (CustomTextView) convertView.findViewById(R.id.dateTextView);
        Date createdAt = mMessages.get(position).getCreatedAt();
        DateFormat df=DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
        dateTextView.setText(df.format(createdAt));

        Button deleteButton = (Button) convertView.findViewById(R.id.deleteButton);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(getActivity())
                        .setTitle(R.string.ALERT_VIEW_WARNING)
                        .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_MESSAGE_DELETION_CONFIRMATION)
                        .setPositiveButton(R.string.ALERT_VIEW_YES, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int id) {
                                deleteMessage(position);
                            }
                        })
                        .setNegativeButton(R.string.ALERT_VIEW_NO, null)
                        .show();
            }
        });

        // set colors accoring if it's read or not
        CustomTextView fromTextView = (CustomTextView) convertView.findViewById(R.id.fromTextView);
        if (message.getUnread()) {
            senderTextView.setTextColor(getResources().getColor(R.color.messages_subjectHighligt));
            dateTextView.setTextColor(getResources().getColor(R.color.messages_unread));
            fromTextView.setTextColor(getResources().getColor(R.color.messages_unread));
        } else {
            senderTextView.setTextColor(getResources().getColor(R.color.messages_read));
            dateTextView.setTextColor(getResources().getColor(R.color.messages_read));
            fromTextView.setTextColor(getResources().getColor(R.color.messages_read));
        }
        return convertView;
    }

    private void deleteMessage(final int position) {
        Message message = mMessages.get(position);
        message.setDeleted(true);
        final ProgressDialog progress = ProgressDialog.show(getActivity(), null, getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_DELETING_MESSAGE));
        message.saveInBackground(new SaveCallback() {
            @Override
            public void done(ParseException e) {
                progress.dismiss();
                if (e == null) {
                    mMessages.remove(position);
                    notifyDataSetChanged();

                } else {
                    Log.d(LOG_TAG, "Error deleting message. " + e.getLocalizedMessage());
                    new AlertDialog.Builder(getActivity())
                            .setTitle(R.string.ALERT_VIEW_ERROR)
                            .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_ERROR_DELETING_MESSAGE)
                            .setPositiveButton(R.string.ALERT_VIEW_OK, null)
                            .show();
                }
            }
        });
    }
}
}

After reading my code I would like to know any suggestion to improve my android skills. Some questions I have:

  • Should I configure item's click after done? Just after I create the adapter. If I do that, I think mAdapter could be a local variable rather than an instance variable. And I guess that's better.

  • Why is setOnItemClickListener working if in that moment the adapter is null?

  • Is it normal to put the deletion logic (even the confirmation dialog) inside the adapter?

  • Is it normal to configure the adapter when I receive the data from network? And setup with the ListView in that moment?

  • Is the adapter like a ViewController form iOS?

\$\endgroup\$
  • \$\begingroup\$ An ArrayAdapter is best thought of as a combination of UITableViewDataSource and UITableViewDelegate. \$\endgroup\$ – W.K.S Sep 14 '15 at 8:31
2
\$\begingroup\$

MessagesListFragment

Firstly, onCreateView should only be used for initializing the view. If you want to perform networks calls, you may do so in onResume():

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View v = inflater.inflate(R.layout.messages_list_layout, container, false);
    final ListView listView = (ListView) v.findViewById(R.id.list);

    listView.setOnItemClickListener(mMessageItemClickListener);

    return v;
}

@Override
public void onResume(){
  super.onResume();

  requestMessages();
}

private void requestMessages(){

  ParseQuery<Message> messagesQuery= ParseQuery.getQuery(Message.class);

  messagesQuery.whereEqualTo("receiver", ParseUser.getCurrentUser());
  messagesQuery.whereEqualTo("deleted", false);
  messagesQuery.include("sender");
  messagesQuery.orderByDescending("createdAt");
  messagesQuery.setLimit(1000);


  final ProgressDialog progress = ProgressDialog.show(getActivity(), null, getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_LOADING_MESSAGES));
  messagesQuery.findInBackground(mFindCallback);

}

For long listeners and callbacks, I usually prefer to make them member variables:

public class MessagesListFragment extends Fragment  {

  //...

  private FindCallback<Message> mFindCallback = new FindCallback<Message>(){

    @Override
    public void done(List<Message> list, ParseException e) {
        progress.dismiss();
        if (e == null) {
            listView.setAdapter(new MessagesAdapter(list));

        } else {
            Log.d(LOG_TAG, "Error loading messages. " + e.getLocalizedMessage());
            new AlertDialog.Builder(getActivity())
                    .setTitle(R.string.ALERT_VIEW_ERROR)
                    .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_ERROR_LOADING_MESSAGES)
                    .setPositiveButton(R.string.ALERT_VIEW_OK, null)
                    .show();
        }
    }

  };
}

Note that it is almost never necessary to make an adapter a member variable. Once you've assigned an adapter, you can access it from the ListView using getAdapter()

private AdapterView.OnItemClickListener mMessageItemClickListener = new AdapterView.OnItemClickListener(){

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      Log.d(LOG_TAG, "onItemClick");
      Message message = (Message) mListView.getItemAtPosition(position);

      // mark message as read if needed
      if (message.getUnread()) {
          message.setUnread(false);
          mAdapter.notifyDataSetChanged();
          message.saveInBackground();
      }

      // show message detail
      Intent messageDetail = new Intent(getActivity(), MessageDetailActivity.class);
      messageDetail.putExtra("message",messageDetail)
      startActivity(messageDetail);
  }

}

Previously, your code had the following line: MessageDetailActivity.message = message; Unfortunately, things aren't as simple on Android as they are on iOS. Kindly follow this tutorial to understand how data needs to be passed between Activities in Android.

MessagesAdapter

Your adapter implementation is mostly correct albeit one glaring error: It is NOT an Adapter's job to handle click events. The adapter should only render the list items. Interaction with the items should be handled in the activity or fragment.

Declare an interface to handle button click events in your Adapter

class MessagesAdapter extends BaseAdapter {

  public interface OnMessageDeleteClickListener(){
    void onMessageDeleteClick(Message message, int position, View view);
  }

  private OnMessageDeleteClickListener mDeleteClickListener;

  public MessagesAdapter(List<Message> messages,OnMessageDeleteClickListener listener ) {
      mMessages=messages;
      mDeleteClickListener = listener;
  }
}

Set this listener to the delete button in getView()

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = View.inflate(getActivity(), R.layout.message_item, null);
    }

    Message message=mMessages.get(position);
    CustomTextView senderTextView = (CustomTextView) convertView.findViewById(R.id.senderTextView);
    senderTextView.setText(getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_MESSAGE_FROM, message.getSender().getString("displayName")));
    CustomTextView dateTextView = (CustomTextView) convertView.findViewById(R.id.dateTextView);
    Date createdAt = mMessages.get(position).getCreatedAt();
    DateFormat df=DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
    dateTextView.setText(df.format(createdAt));

    Button deleteButton = (Button) convertView.findViewById(R.id.deleteButton);
    deleteButton.setOnClickListener(new View.OnClickListener(){

      @Override
      public void onClick(View view){
        if(mDeleteClickListener != null){
          mDeleteClickListener.onMessageDeleteClick(message,position,view);
        }
      }

    });

    // set colors accoring if it's read or not
    CustomTextView fromTextView = (CustomTextView)      
    convertView.findViewById(R.id.fromTextView);

    int color = getResources().getColor(message.getUnread()? R.color.messages_unread : R.color.messages_read);

    senderTextView.setTextColor(color);
    dateTextView.setTextColor(color);
    fromTextView.setTextColor(color);

    return convertView;
  }

and implement the listener in your activity:

public class MessagesListFragment extends Fragment  {

  private FindCallback<Message> mFindCallback = new FindCallback<Message>(){
    @Override
    public void done(List<Message> list, ParseException e) {
        progress.dismiss();
        if (e == null) {
            listView.setAdapter(new MessagesAdapter(list,mDeleteClickListener));

        } else {
            Log.d(LOG_TAG, "Error loading messages. " + e.getLocalizedMessage());
            new AlertDialog.Builder(getActivity())
                    .setTitle(R.string.ALERT_VIEW_ERROR)
                    .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_ERROR_LOADING_MESSAGES)
                    .setPositiveButton(R.string.ALERT_VIEW_OK, null)
                    .show();
        }
    }

  };

  private OnMessageDeleteClickListener mDeleteClickListener = new OnMessageDeleteClickListener(){
    void onMessageDeleteClick(Message message, int position, View view){
      message.setDeleted(true);
      final ProgressDialog progress = ProgressDialog.show(getActivity(), null, getString(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_DELETING_MESSAGE));
      message.saveInBackground(new SaveCallback() {
          @Override
          public void done(ParseException e) {
              progress.dismiss();
              if (e == null) {
                  mMessages.remove(position);
                  notifyDataSetChanged();

              } else {
                  Log.d(LOG_TAG, "Error deleting message. " + e.getLocalizedMessage());
                  new AlertDialog.Builder(getActivity())
                          .setTitle(R.string.ALERT_VIEW_ERROR)
                          .setMessage(R.string.MESSAGES_TABLE_VIEW_CONTROLLER_ERROR_DELETING_MESSAGE)
                          .setPositiveButton(R.string.ALERT_VIEW_OK, null)
                          .show();
              }
          }
      });
    }
  };

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.