Making the address

QQDemo

Project introduction

This project is an example project of instant messaging. It uses MVP mode, integrates huanxing SDK and Bmob back-end cloud, and shows the realization of basic functions of instant messaging, including registering and logging in, logging out, contact list, adding friends, deleting friends, sending and receiving messages, message reminding and other functions.

Open source projects used

  • BottomBar
  • EventBus
  • greenDAO
  • butterknife

Learning goals

  • Integration and use of HUANxin SDK
  • MVP mode
  • Integration and use of ORM database
  • The use of modular ideas

Instant Messaging

Allows two or more people to use the Internet for instant text messaging, file, voice and video communication.

Related products

  • Doyen ICQ
  • Domestic mainstream QQ, Wechat, momo, YY, etc
  • Facebook Messenger WhatsApp Skype Instagram Line

Third Party Service Platform

  • Ring letter
  • Integrating cloud
  • Netease cloud letter
  • The aurora IM
  • Tencent Cloud communication IM
  • Love ‘
  • Ali Wukong (officially offline on October 31, 2016)
  • Ali is prosperous

Ring letter

Im Cloud 3.x documentation




easemob_business.png

Ring letter integration

  1. Register and create an application
  2. Download the SDK
  3. The introduction of the SDK
  4. Initialize the SDK

.so the folder

  1. On jniLibs
  2. You can also put it in the libs directory, but you need to configure it in the configuration file under the module

     android {
         sourceSets {
             main {
                 jniLibs.srcDirs = ['libs']
             }
         }
     }Copy the code

crater

Run error: Didn ‘t find the class “com. Hyphenate. Chat. Adapter. EMACallSession”, the reason is hyphenatechat_3. 2.0 the jar package without the class.

Solution: Import Demo source EaseUI library hyphenatechat_3.2.0.jar replacement.

Software architecture

MVC

MVC is used in Ruby on Rails, Spring Framework, iOS development and ASP.NET.

  • Model: Business logic to obtain data, network operation, database operation
  • View: UI
  • Controller: Manipulate the Model layer to fetch data to pass to the UI



mvc.png

Server-side MVC




mvcpattern.png

Android in MVC

Android does not have a clear MVC framework. If an Activity is regarded as a Controller, according to our actual development experience, there will be a lot of UI operations in it, so it will be silly to distinguish V from C.

  • Model:Java Bean, NetworkManager, DataBaseHelper
  • View: xml res
  • Controller: Activity Fragment
  • ArrayList-ListView-Adapter(MVC)

MVP

MVP is mainly used in ASP.NET. The main difference between MVP and MVC is that the View and Model are no longer coupled.

  • Model: Business logic to obtain data, network operation, database operation
  • View: UI
  • Presenter: Manipulate the Model layer to get data to pass to the UI



mvp.png

MVVM

MVVM is mainly used in WPF, Silverlight, Caliburn, nRoute, etc.

  • Model: Business logic to obtain data, network operation, database operation
  • View: UI
  • ViewModel: Bind the View to the Model



mvvm.png

The Android MVVM

Data Binding Library

The core ideas of software architecture

Stratified module




architecture.png

reference

android architecture

How to choose between MVC,MVP and MVVM patterns

Understanding MVC, MVP and MVVM Design Patterns

Learn about MVC, MVP and MVVM

Android Data Binding

Clean Architecture

Are you ready? Start driving!!

The creation of a package

  • Adapter storage adapter
  • App holds constant classes, Application classes, and some global classes at the app level
  • Database Database related classes
  • Event EventBus Event class
  • The factory factory class
  • Model Data model
  • Presenter Class in the PRESENTER MVP model
  • UI stores activities and fragments
  • Utils tools
  • View class in the VIEW MVP model
  • Widget Custom controls

Base class creation

  • BaseActivity
  • BaseFragment

Git initialization

Splash screen




splash.png

The functional requirements

  1. If you do not log in, the login page is displayed after 2 seconds
  2. If you have logged in, the main window is displayed

MVP implementation

  • SplashView
  • SplashPresenter

Login screen




login.jpg

The functional requirements

  1. You can initiate a login operation either by clicking the login button or by clicking the Action key on the virtual keyboard.
  2. Click New User to jump to the registration interface.

IME Options

Note that the imeOptions property of the EditText must be configured with inputType to take effect.

Android :imeOptions="actionNext"// Next Android :imeOptions="actionGo"// Start Android :imeOptions="actionDone"// done Android :imeOptions="actionPrevious"// Previous Android :imeOptions="actionSearch"// Search android:imeOptions="actionSend"// SendCopy the code

MVP implementation

An adapter for EMCallBack

public class EMCallBackAdapter implements EMCallBack{

    @Override
    public void onSuccess() {

    }

    @Override
    public void onError(int i, String s) {

    }

    @Override
    public void onProgress(int i, String s) {

    }
}Copy the code

Android6.0 Dynamic permission management

For example: Autonavi Map baidu Map and so on

/ * * * if there is a write disk access * / private Boolean hasWriteExternalStoragePermission () {int result = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); return result == PermissionChecker.PERMISSION_GRANTED; } / apply for permission to * * * * / private void applyPermission () {String [] permissions = {Manifest. Permission. WRITE_EXTERNAL_STORAGE}; ActivityCompat.requestPermissions(this, permissions, REQUEST_WRITE_EXTERNAL_STORAGE); } / / @ * * * to apply for permission to callback * Override public void onRequestPermissionsResult (int requestCode, @ NonNull String [] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_WRITE_EXTERNAL_STORAGE: if (grantResults[0] == PermissionChecker.PERMISSION_GRANTED) { login(); } else { toast(getString(R.string.not_get_permission)); } break; }}Copy the code

The registration screen




register.jpg

The functional requirements

  1. The user name must be 3 to 20 characters in length. It must start with an English character and contain digits or underscores (_).
  2. The password must be 3-20 digits.
  3. The password is the same as the confirm password

Regular expression

Regular expression – metacharacters

Private static final String USER_NAME_REGEX = "^[a-za-z]\\w{2,19}$"; Private static final String PASSWORD_REGEX = "^[0-9]{3,20}$"; private static final String PASSWORD_REGEX = "^[0-9]{3,20}$";Copy the code
  • \w matches any word character that includes underscores. Equivalent to ‘[a-za-z0-9_]’.

MVP implementation

  • RegisterView
  • RegisterPresenter

The registration process

  1. In a real project, the registration would register the user name and password with the APP server, which would then register with the ring server through the REST API.
  2. Since there is no APP server in this project, the user data will be registered with the third-party cloud database Bmob. After successful registration, a request will be sent on the client side to register with the ring mail server.



register_logic.png

Cloud database

  • LeanCloud
  • Bmob
  • Parse(closed January 28, 2017)

Bmob integration

Development of the document

  1. Registering and creating an Application
  2. Download the SDK
  3. Import the SDK
  4. To initialize the SDk

Hidden soft keyboard

protected void hideSoftKeyboard() {
    if (mInputMethodManager == null) {
        mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
    }
    mInputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}Copy the code

Soft keyboard Action processing

private TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_GO) { reigister(); // register return true; } return false; }};Copy the code

Processing of registered user names

Bmob error code

The main interface




main.jpg

Bottom navigation bar

RadioGroup, TabHost, FragmentTabHost, custom

Third party bottom bar

BottomBar

AHBottomNavigation

BottomNavigation

Fragments of the switch

A dynamic interface




logout.jpg

Contact screen




contact1.jpg




contact2.jpg

MVP implementation

  • ContactView
  • ContactPresenter

The use of RecyclerView

Creating Lists and Cards

Whether the contacts are in the same group

private boolean itemInSameGroup(int i, ContactItem item) {
    return i > 0 && (item.getFirstLetter() == mContactItems.get(i - 1).getFirstLetter());
}Copy the code

The use of the CardView

The use of SwipeRefreshLayout

mSwipeRefreshLayout.setColorSchemeResources(R.color.qq_blue, R.color.qq_red);
mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);Copy the code

Custom control SlideBar

Class character array (for copy)

private static final String[] SECTIONS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"
        , "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};Copy the code

Draws centered text

www.cnblogs.com/tianzhijiex…

Listen for SlideBar events in the ContactFragment

private SlideBar.OnSlideBarChangeListener mOnSlideBarChangeListener = new SlideBar.OnSlideBarChangeListener() { @Override public void onSectionChange(int index, String section) { mSection.setVisibility(View.VISIBLE); mSection.setText(section); scrollToSection(section); } @Override public void onSlidingFinish() { mSection.setVisibility(View.GONE); }}; Private void scrollToSection(String section) {int; private void scrollToSection(String section) {private void scrollToSection(String section) {private void scrollToSection(String section) {int sectionPosition = getSectionPosition(section); if (sectionPosition ! = POSITION_NOT_FOUND) { mRecyclerView.smoothScrollToPosition(sectionPosition); }} /** ** @param Section first character * @return The first character in the contact list is the position of the section's first contact in the contact list */ private int getSectionPosition(String) section) { List contactItems = mContactListAdapter.getContactItems(); for (int i = 0; i < contactItems.size(); i++) { if (section.equals(contactItems.get(i).getFirstLetterString())) { return i; } } return POSITION_NOT_FOUND; }Copy the code

Contact click event

private ContactListAdapter.OnItemClickListener mOnItemClickListener = new ContactListAdapter.OnItemClickListener() { /** @override public void onItemClick(String name) {@override public void onItemClick(String name) { startActivity(ChatActivity.class, Constant.Extra.USER_NAME, name); @override public void onItemLongClick(final String name) {/** * public void onItemLongClick(final String name) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); String message = String.format(getString(R.string.delete_friend_message), name); builder.setTitle(getString(R.string.delete_friend)) .setMessage(message) .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); showProgress(getString(R.string.deleting_friend)); mContactPresenter.deleteFriend(name); }}); builder.show(); }};Copy the code

Add Friends screen




add_friend.jpg

Search the user

Query data

@Override public void searchFriend(final String keyword) { mAddFriendView.onStartSearch(); // Note: Fuzzy query is only available to paid users. BmobQuery query = new BmobQuery(); query.addWhereContains("username", keyword).addWhereNotEqualTo("username", EMClient.getInstance().getCurrentUser()); query.findObjects(new FindListener() { @Override public void done(List list, BmobException e) { processResult(list, e); }}); }Copy the code

greenDAO

GreenDAO is a kind of ORM framework for Android SQLite database. ORM stands for Object/Relational Mapping, which maps Java objects into database tables.

Other ORM frameworks

  • DBFlow
  • Ormlite
  • Sugar
  • ActiveAndroid
  • Sprinkles
  • Ollie

reference

  • Github
  • website
  • AppBrain
  • Using document
  • Chinese Usage Document

Creating an entity Class

@Entity
public class Contact {

    @Id
    public Long id;

    public String userName;
}Copy the code

Initialize the

public void init(Context context) {
    DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context, Constant.Database.DATABASE_NAME, null);
    SQLiteDatabase writableDatabase = devOpenHelper.getWritableDatabase();
    DaoMaster daoMaster = new DaoMaster(writableDatabase);
    mDaoSession = daoMaster.newSession();
}Copy the code

Save contacts

public void saveContact(String userName) {
    Contact contact = new Contact();
    contact.setUsername(userName);
    mDaoSession.getContactDao().save(contact);
}Copy the code

Querying Contacts

public List queryAllContacts() {
    List list = mDaoSession.getContactDao().queryBuilder().list();
    ArrayList contacts = new ArrayList();
    for (int i = 0; i < list.size(); i++) {
        String contact = list.get(i).getUsername();
        contacts.add(contact);
    }
    return contacts;
}Copy the code

Deleting contacts

public void deleteAllContacts() {
    ContactDao contactDao = mDaoSession.getContactDao();
    contactDao.deleteAll();
}Copy the code

Send a Friend Request

AddFriendItemView handles click events

@OnClick(R.id.add)
public void onClick() {
    String friendName = mUserName.getText().toString().trim();
    String addFriendReason = getContext().getString(R.string.add_friend_reason);
    AddFriendEvent event = new AddFriendEvent(friendName, addFriendReason);
    EventBus.getDefault().post(event);
}Copy the code

AddFriendPresenterImpl enables you to send a friend request

@Subscribe(threadMode = ThreadMode.BACKGROUND) public void addFriend(AddFriendEvent event) { try { EMClient.getInstance().contactManager().addContact(event.getFriendName(), event.getReason()); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mAddFriendView.onAddFriendSuccess(); }}); } catch (HyphenateException e) { e.printStackTrace(); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mAddFriendView.onAddFriendFailed(); }}); }Copy the code

Listen for contact changes in the contact list

private EMContactListenerAdapter mEMContactListener = new EMContactListenerAdapter() { @Override public void onContactAdded(String s) { mContactPresenter.refreshContactList(); } @Override public void onContactDeleted(String s) { mContactPresenter.refreshContactList(); }};Copy the code

Chat screen




chat1.jpg




chat2.jpg

Listen for state changes for the send button

mEdit.addTextChangedListener(mTextWatcher);
private TextWatcherAdapter mTextWatcher = new TextWatcherAdapter() {
    @Override
    public void afterTextChanged(Editable s) {
        mSend.setEnabled(s.length() != 0);
    }
};Copy the code

Animation files

  • Anim folder: Store tween animations
  • Animator folder: Holds property animations
  • Drawable folder: Holds frame animations

Send a progress animation of the message



    
    
    
    
    
    
    
    
Copy the code

9 File Making

The official instructions




ninepatch_raw.png




ninepatch_examples.png

Send a message

The type of message returned

@Override
public int getItemViewType(int position) {
    EMMessage message = mMessages.get(position);
    return message.direct() == EMMessage.Direct.SEND ? ITEM_TYPE_SEND_MESSAGE : ITEM_TYPE_RECEIVE_MESSAGE;
}Copy the code

Determine whether to display a timestamp

/** * If the time between two messages is too close, */ private Boolean shouldShowTimeStamp(int position) {long currentItemTimestamp = mMessages.get(position).getMsgTime(); long preItemTimestamp = mMessages.get(position - 1).getMsgTime(); boolean closeEnough = DateUtils.isCloseEnough(currentItemTimestamp, preItemTimestamp); return ! closeEnough; }Copy the code

Update the status of the message

private void updateSendingStatus(EMMessage emMessage) { switch (emMessage.status()) { case INPROGRESS: mSendMessageProgress.setVisibility(VISIBLE); mSendMessageProgress.setImageResource(R.drawable.send_message_progress); AnimationDrawable drawable = (AnimationDrawable) mSendMessageProgress.getDrawable(); drawable.start(); break; case SUCCESS: mSendMessageProgress.setVisibility(GONE); break; case FAIL: mSendMessageProgress.setImageResource(R.mipmap.msg_error); break; }}Copy the code

Receive a message

private EMMessageListenerAdapter mEMMessageListener = new EMMessageListenerAdapter() { @Override public void onMessageReceived(final List list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { final EMMessage emMessage = list.get(0); mChatPresenter.makeMessageRead(mUserName); mMessageListAdapter.addNewMessage(emMessage); smoothScrollToBottom(); }}); }};Copy the code

Initialize the chat history

The official documentation

Communication process and chat record saving

@Override public void loadMessages(final String userName) { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override  public void run() { EMConversation conversation = EMClient.getInstance().chatManager().getConversation(userName); if (conversation ! = null) {/ / get all the messages of this session List messages = conversation. GetAllMessages (); mEMMessageList.addAll(messages); / / the specified session message without reading reset conversation. MarkAllMessagesAsRead (); } ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mChatView.onMessagesLoaded(); }}); }}); }Copy the code

Load more chats

@Override public void loadMoreMessages(final String userName) { if (hasMoreData) { ThreadUtils.runOnBackgroundThread(new  Runnable() { @Override public void run() { EMConversation conversation = EMClient.getInstance().chatManager().getConversation(userName); EMMessage firstMessage = mEMMessageList.get(0); // Get the pagesize message before startMsgId. The messages obtained by this method will be automatically saved into this session. In the APP without access to messages added to the conversation again final List messages = conversation. LoadMoreMsgFromDB (firstMessage. GetMsgId (), DEFAULT_PAGE_SIZE); hasMoreData = (messages.size() == DEFAULT_PAGE_SIZE); mEMMessageList.addAll(0, messages); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mChatView.onMoreMessagesLoaded(messages.size()); }}); }}); } else { mChatView.onNoMoreData(); }}Copy the code

The session interface




message.jpg

MVP implementation

  • ConversationView
  • ConversationPresenter

Loading all sessions

@Override public void loadAllConversations() { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override public void run() { Map conversations = EMClient.getInstance().chatManager().getAllConversations(); mEMConversations.addAll(conversations.values()); Collections.sort(mEMConversations, new Comparator() { @Override public int compare(EMConversation o1, EMConversation o2) { return (int) (o2.getLastMessage().getMsgTime() - o1.getLastMessage().getMsgTime()); }}); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mConversationView.onAllConversationsLoaded(); }}); }}); }Copy the code

Unread message count updated

The official documentation

Updates of unread messages in the session list

private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { toast(getString(R.string.receive_new_message)); mConversationPresenter.loadAllConversations(); }}); }};Copy the code

Update to BottomBar Bardge

Private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter () {/ / the callback call @ Override in the child thread public void onMessageReceived(List list) { updateUnreadCount(); }}; private void updateUnreadCount() { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { BottomBarTab  bottomBar = mBottomBar.getTabWithId(R.id.conversations); int count = EMClient.getInstance().chatManager().getUnreadMsgsCount(); bottomBar.setBadgeCount(count); }}); }Copy the code

Marks the message read

Marked read while loading chat data

/ / the specified session message without reading reset conversation. MarkAllMessagesAsRead ();Copy the code

Marks a new message as read when it is received in chat

Chat interface returns when updated without reading badge

@Override
protected void onResume() {
    super.onResume();
    updateUnreadCount();
}Copy the code

alerts

notice

Determine if the app is in the foreground

public boolean isForeground() {
    ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    List runningAppProcesses = am.getRunningAppProcesses();
    if (runningAppProcesses == null) {
        return false;
    }
    for (ActivityManager.RunningAppProcessInfo info :runningAppProcesses) {
        if (info.processName.equals(getPackageName()) && info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
            return true;
        }
    }
    return false;
}Copy the code

Notification pops up if the app is in the background

private void showNotification(EMMessage emMessage) {
    String contentText = "";
    if (emMessage.getBody() instanceof EMTextMessageBody) {
        contentText = ((EMTextMessageBody) emMessage.getBody()).getMessage();
    }

    Intent chat = new Intent(this, ChatActivity.class);
    chat.putExtra(Constant.Extra.USER_NAME, emMessage.getUserName());
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, chat, PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification notification = new Notification.Builder(this)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.avatar1))
            .setSmallIcon(R.mipmap.ic_contact_selected_2)
            .setContentTitle(getString(R.string.receive_new_message))
            .setContentText(contentText)
            .setPriority(Notification.PRIORITY_MAX)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .build();
    notificationManager.notify(1, notification);
}Copy the code

voice

Initialize the SoundPool

private void initSoundPool() {
    mSoundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0);
    mDuanSound = mSoundPool.load(this, R.raw.duan, 1);
    mYuluSound = mSoundPool.load(this, R.raw.yulu, 1);
}Copy the code

Play sound

private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List list) { if (isForeground()) { mSoundPool.play(mDuanSound, 1, 1, 0, 0, 1); } else { mSoundPool.play(mYuluSound, 1, 1, 0, 0, 1); showNotification(list.get(0)); }}};Copy the code

Multi-device Login

The official documentation

private EMConnectionListener mEMConnectionListener = new EMConnectionListener() { @Override public void onConnected() { } @Override public void onDisconnected(int i) { if (i == EMError.USER_LOGIN_ANOTHER_DEVICE) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { startActivity(LoginActivity.class); toast(getString(R.string.user_login_another_device)); }}); }}};Copy the code