– Download the library files for Smack API and GSON which is a library for converting java objects to JSON strings.
– Copy the jar files into the libs folder of your android project. If this folder does not exists then create it.
– Right click on the jar files, go to Build Path -> Add to Build Path. Repeat this step for each .jar file in the libs folder.
Adding third party libraries:
– Download this file and extract it to the src folder of your project directory. This step will add new packages along with your existing packages in the src directory. These packages are required by smack library as third party libraries.
Now we have successfully added all the required library files to our android project.
Create a new java file named MyXMPP.java in your com.marothiatechs.mchat package in src folder.
We will write the implementation of smack library in this file and will use this file to initialise connections to XMPP server and also to send and receive chat messages.
The content of the MyXMPP.java goes as follows:
package com.marothiatechs.mchat; import java.io.IOException; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat.ChatManager; import org.jivesoftware.smack.chat.ChatManagerListener; import org.jivesoftware.smack.chat.ChatMessageListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.receipts.DeliveryReceiptManager; import org.jivesoftware.smackx.receipts.DeliveryReceiptManager.AutoReceiptMode; import org.jivesoftware.smackx.receipts.ReceiptReceivedListener; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.widget.Toast; import com.google.gson.Gson; import com.marothiatechs.fragments.Chats; public class MyXMPP { public static boolean connected = false; public boolean loggedin = false; public static boolean isconnecting = false; public static boolean isToasted = true; private boolean chat_created = false; private String serverAddress; public static XMPPTCPConnection connection; public static String loginUser; public static String passwordUser; Gson gson; MyService context; public static MyXMPP instance = null; public static boolean instanceCreated = false; public MyXMPP(MyService context, String serverAdress, String logiUser, String passwordser) { this.serverAddress = serverAdress; this.loginUser = logiUser; this.passwordUser = passwordser; this.context = context; init(); } public static MyXMPP getInstance(MyService context, String server, String user, String pass) { if (instance == null) { instance = new MyXMPP(context, server, user, pass); instanceCreated = true; } return instance; } public org.jivesoftware.smack.chat.Chat Mychat; ChatManagerListenerImpl mChatManagerListener; MMessageListener mMessageListener; String text = ""; String mMessage = "", mReceiver = ""; static { try { Class.forName("org.jivesoftware.smack.ReconnectionManager"); } catch (ClassNotFoundException ex) { // problem loading reconnection manager } } public void init() { gson = new Gson(); mMessageListener = new MMessageListener(context); mChatManagerListener = new ChatManagerListenerImpl(); initialiseConnection(); } private void initialiseConnection() { XMPPTCPConnectionConfiguration.Builder config = XMPPTCPConnectionConfiguration .builder(); config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); config.setServiceName(serverAddress); config.setHost(serverAddress); config.setPort(5222); config.setDebuggerEnabled(true); XMPPTCPConnection.setUseStreamManagementResumptiodDefault(true); XMPPTCPConnection.setUseStreamManagementDefault(true); connection = new XMPPTCPConnection(config.build()); XMPPConnectionListener connectionListener = new XMPPConnectionListener(); connection.addConnectionListener(connectionListener); } public void disconnect() { new Thread(new Runnable() { @Override public void run() { connection.disconnect(); } }).start(); } public void connect(final String caller) { AsyncTask<Void, Void, Boolean> connectionThread = new AsyncTask<Void, Void, Boolean>() { @Override protected synchronized Boolean doInBackground(Void... arg0) { if (connection.isConnected()) return false; isconnecting = true; if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(context, caller + "=>connecting....", Toast.LENGTH_LONG).show(); } }); Log.d("Connect() Function", caller + "=>connecting...."); try { connection.connect(); DeliveryReceiptManager dm = DeliveryReceiptManager .getInstanceFor(connection); dm.setAutoReceiptMode(AutoReceiptMode.always); dm.addReceiptReceivedListener(new ReceiptReceivedListener() { @Override public void onReceiptReceived(final String fromid, final String toid, final String msgid, final Stanza packet) { } }); connected = true; } catch (IOException e) { if (isToasted) new Handler(Looper.getMainLooper()) .post(new Runnable() { @Override public void run() { Toast.makeText( context, "(" + caller + ")" + "IOException: ", Toast.LENGTH_SHORT).show(); } }); Log.e("(" + caller + ")", "IOException: " + e.getMessage()); } catch (SmackException e) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(context, "(" + caller + ")" + "SMACKException: ", Toast.LENGTH_SHORT).show(); } }); Log.e("(" + caller + ")", "SMACKException: " + e.getMessage()); } catch (XMPPException e) { if (isToasted) new Handler(Looper.getMainLooper()) .post(new Runnable() { @Override public void run() { Toast.makeText( context, "(" + caller + ")" + "XMPPException: ", Toast.LENGTH_SHORT).show(); } }); Log.e("connect(" + caller + ")", "XMPPException: " + e.getMessage()); } return isconnecting = false; } }; connectionThread.execute(); } public void login() { try { connection.login(loginUser, passwordUser); Log.i("LOGIN", "Yey! We're connected to the Xmpp server!"); } catch (XMPPException | SmackException | IOException e) { e.printStackTrace(); } catch (Exception e) { } } private class ChatManagerListenerImpl implements ChatManagerListener { @Override public void chatCreated(final org.jivesoftware.smack.chat.Chat chat, final boolean createdLocally) { if (!createdLocally) chat.addMessageListener(mMessageListener); } } public void sendMessage(ChatMessage chatMessage) { String body = gson.toJson(chatMessage); if (!chat_created) { Mychat = ChatManager.getInstanceFor(connection).createChat( chatMessage.receiver + "@" + context.getString(R.string.server), mMessageListener); chat_created = true; } final Message message = new Message(); message.setBody(body); message.setStanzaId(chatMessage.msgid); message.setType(Message.Type.chat); try { if (connection.isAuthenticated()) { Mychat.sendMessage(message); } else { login(); } } catch (NotConnectedException e) { Log.e("xmpp.SendMessage()", "msg Not sent!-Not Connected!"); } catch (Exception e) { Log.e("xmpp.SendMessage()-Exception", "msg Not sent!" + e.getMessage()); } } public class XMPPConnectionListener implements ConnectionListener { @Override public void connected(final XMPPConnection connection) { Log.d("xmpp", "Connected!"); connected = true; if (!connection.isAuthenticated()) { login(); } } @Override public void connectionClosed() { if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Toast.makeText(context, "ConnectionCLosed!", Toast.LENGTH_SHORT).show(); } }); Log.d("xmpp", "ConnectionCLosed!"); connected = false; chat_created = false; loggedin = false; } @Override public void connectionClosedOnError(Exception arg0) { if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(context, "ConnectionClosedOn Error!!", Toast.LENGTH_SHORT).show(); } }); Log.d("xmpp", "ConnectionClosedOn Error!"); connected = false; chat_created = false; loggedin = false; } @Override public void reconnectingIn(int arg0) { Log.d("xmpp", "Reconnectingin " + arg0); loggedin = false; } @Override public void reconnectionFailed(Exception arg0) { if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(context, "ReconnectionFailed!", Toast.LENGTH_SHORT).show(); } }); Log.d("xmpp", "ReconnectionFailed!"); connected = false; chat_created = false; loggedin = false; } @Override public void reconnectionSuccessful() { if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Toast.makeText(context, "REConnected!", Toast.LENGTH_SHORT).show(); } }); Log.d("xmpp", "ReconnectionSuccessful"); connected = true; chat_created = false; loggedin = false; } @Override public void authenticated(XMPPConnection arg0, boolean arg1) { Log.d("xmpp", "Authenticated!"); loggedin = true; ChatManager.getInstanceFor(connection).addChatListener( mChatManagerListener); chat_created = false; new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); if (isToasted) new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Toast.makeText(context, "Connected!", Toast.LENGTH_SHORT).show(); } }); } } private class MMessageListener implements ChatMessageListener { public MMessageListener(Context contxt) { } @Override public void processMessage(final org.jivesoftware.smack.chat.Chat chat, final Message message) { Log.i("MyXMPP_MESSAGE_LISTENER", "Xmpp message received: '" + message); if (message.getType() == Message.Type.chat && message.getBody() != null) { final ChatMessage chatMessage = gson.fromJson( message.getBody(), ChatMessage.class); processMessage(chatMessage); } } private void processMessage(final ChatMessage chatMessage) { chatMessage.isMine = false; Chats.chatlist.add(chatMessage); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Chats.chatAdapter.notifyDataSetChanged(); } }); } } }
As you can see in the source code, we have created methods for initialising, connecting, sending messages as well as a listener for processing received messages.
In android long running processes has to be implemented in background. So we will use Android Service for our requirements.
Creating Android Service:
Create a file named LocalBinder.java and copy the below contents:
package com.marothiatechs.mchat; import java.lang.ref.WeakReference; import android.os.Binder; public class LocalBinder<S> extends Binder { private final WeakReference<S> mService; public LocalBinder(final S service) { mService = new WeakReference<S>(service); } public S getService() { return mService.get(); } }
Now save this file into the package com.marothiatechs.mchat in src directory.
Create another file name MyService.java and copy the code :
package com.marothiatechs.mchat; import org.jivesoftware.smack.chat.Chat; import android.app.Service; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.os.IBinder; public class MyService extends Service { private static final String DOMAIN = "xmpp.jp"; private static final String USERNAME = "khushi"; private static final String PASSWORD = "password"; public static ConnectivityManager cm; public static MyXMPP xmpp; public static boolean ServerchatCreated = false; String text = ""; @Override public IBinder onBind(final Intent intent) { return new LocalBinder<MyService>(this); } public Chat chat; @Override public void onCreate() { super.onCreate(); cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); xmpp = MyXMPP.getInstance(MyService.this, DOMAIN, USERNAME, PASSWORD); xmpp.connect("onCreate"); } @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { return Service.START_NOT_STICKY; } @Override public boolean onUnbind(final Intent intent) { return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); xmpp.connection.disconnect(); } public static boolean isNetworkConnected() { return cm.getActiveNetworkInfo() != null; } }
MyService.java is the class for android service and it will be running in the background. You can learn more about services here.
Now modify the contents of our MainActivity.java to this:
package com.marothiatechs.mchat; import java.util.Locale; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import com.marothiatechs.fragments.Chats; public class MainActivity extends ActionBarActivity implements ActionBar.TabListener { private static final String TAG = "MainActivity"; private boolean mBounded; SectionsPagerAdapter mSectionsPagerAdapter; ViewPager mViewPager; private MyService mService; private final ServiceConnection mConnection = new ServiceConnection() { @SuppressWarnings("unchecked") @Override public void onServiceConnected(final ComponentName name, final IBinder service) { mService = ((LocalBinder<MyService>) service).getService(); mBounded = true; Log.d(TAG, "onServiceConnected"); } @Override public void onServiceDisconnected(final ComponentName name) { mService = null; mBounded = false; Log.d(TAG, "onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); doBindService(); final ActionBar actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); mSectionsPagerAdapter = new SectionsPagerAdapter( getSupportFragmentManager()); mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mSectionsPagerAdapter); mViewPager .setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { actionBar.setSelectedNavigationItem(position); } }); // For each of the sections in the app, add a tab to the action bar. for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) { actionBar.addTab(actionBar.newTab() .setText(mSectionsPagerAdapter.getPageTitle(i)) .setTabListener(this)); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { mViewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { } public class SectionsPagerAdapter extends FragmentPagerAdapter { public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { if (position == 1) { return new Chats(); } else return PlaceholderFragment.newInstance(position + 1); } @Override public int getCount() { // Show 3 total pages. return 3; } @Override public CharSequence getPageTitle(int position) { Locale l = Locale.getDefault(); switch (position) { case 0: return getString(R.string.title_section1).toUpperCase(l); case 1: return getString(R.string.title_section2).toUpperCase(l); case 2: return getString(R.string.title_section3).toUpperCase(l); } return null; } } public static class PlaceholderFragment extends Fragment { private static final String ARG_SECTION_NUMBER = "section_number"; public static PlaceholderFragment newInstance(int sectionNumber) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); return rootView; } } @Override protected void onDestroy() { super.onDestroy(); doUnbindService(); } void doBindService() { bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE); } void doUnbindService() { if (mConnection != null) { unbindService(mConnection); } } public MyService getmService() { return mService; } }
Now as we have implemented android service and connection to xmpp server is done inside the service, we have to use our implementation to send messages using xmpp class instead of simply adding an entry to the listview in the chat_layout.xml of Chats.java
So modify the sendTextMessage() method in Chats.java in package com.marothiatechs.fragments to send messages using xmpp.
So the final code for Chats.java will be :
package com.marothiatechs.fragments; import java.util.ArrayList; import java.util.Random; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ListView; import com.marothiatechs.mchat.ChatAdapter; import com.marothiatechs.mchat.ChatMessage; import com.marothiatechs.mchat.CommonMethods; import com.marothiatechs.mchat.MainActivity; import com.marothiatechs.mchat.R; public class Chats extends Fragment implements OnClickListener { private EditText msg_edittext; private String user1 = "khushi", user2 = "khushi";// chating with self private Random random; public static ArrayList<ChatMessage> chatlist; public static ChatAdapter chatAdapter; ListView msgListView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.chat_layout, container, false); random = new Random(); ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle( "Chats"); msg_edittext = (EditText) view.findViewById(R.id.messageEditText); msgListView = (ListView) view.findViewById(R.id.msgListView); ImageButton sendButton = (ImageButton) view .findViewById(R.id.sendMessageButton); sendButton.setOnClickListener(this); // ----Set autoscroll of listview when a new message arrives----// msgListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); msgListView.setStackFromBottom(true); chatlist = new ArrayList<ChatMessage>(); chatAdapter = new ChatAdapter(getActivity(), chatlist); msgListView.setAdapter(chatAdapter); return view; } @Override public void onSaveInstanceState(Bundle outState) { } public void sendTextMessage(View v) { String message = msg_edittext.getEditableText().toString(); if (!message.equalsIgnoreCase("")) { final ChatMessage chatMessage = new ChatMessage(user1, user2, message, "" + random.nextInt(1000), true); chatMessage.setMsgID(); chatMessage.body = message; chatMessage.Date = CommonMethods.getCurrentDate(); chatMessage.Time = CommonMethods.getCurrentTime(); msg_edittext.setText(""); chatAdapter.add(chatMessage); chatAdapter.notifyDataSetChanged(); MainActivity activity = ((MainActivity) getActivity()); activity.getmService().xmpp.sendMessage(chatMessage); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.sendMessageButton: sendTextMessage(v); } } }
Now the java coding part is done and we need to modify our AndroidManifest.xml file to add certain permissions and to declare MyService.
So the modified AndroidManifest.xml is :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.marothiatechs.mchat" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService" android:enabled="true" /> </application> </manifest>
The final project hierarchy should look like this:
Now run the application and go to Chats tab.
Try to send any message and it will come back to you because we are sending it to ourself i.e we used same username for both sender and receiver in our code.
The screenshot for the running app:
Simply change the user2 variable in Chats.java to other username if you want to chat with other users.
It was just a simple app to show the implementation of Smack 4.1 API in android. You can further modify the project according to your needs to develop a fully functional messenger.
You can download a demo apk file of the tutorial here.
241,931 total views, 5 views today
Pingback: Building your own android chat messenger app similar to Whatsapp using XMPP (Smack 4.1 API) from scratch: Part-1 | TutorialsFace()
Pingback: Building your own android chat messenger app similar to Whatsapp using XMPP (Smack 4.1 API) from scratch: Part-1 - TutorialsFace()