Android’de Soket Programlama ile Chat Odası Oluşturma

İnsanlar birbirleriyle iletişime geçebilmek için Facebook, Whatsapp gibi uygulamalardan önce chat odalarını kullanıyorlardı. Bu chat odaları bilgisayarlar ya da mobil uygulamalar üzerinden günümüzde halen kullanılmaktadır. Bu nedenle bugün ki makalemde bir Android uygulamada Soket programlama yaparak Chat odası oluşturmayı örnekleyeceğim.

Fakat öncelikle Soket programlamaya kısaca değinmek istiyorum.

Soketler, bir tür süreçler arası haberleşme (interprocessing) yöntemidir. Soket, soyut bir tanımla haberleşme uç noktalarıdır. Pratik olarak soketler dosyalara benzer. Soketten okumak ile dosyadan okumak arasında hiçbir fark yoktur.Aklınıza gelebilecek hemen her internet programı socket program olarak çalışır.

Hangi teknolojileri kullanacağız?

Android uygulamada Soket programlama ile chat odası oluşturmayı basit hale getirmek için Socket.IO kütüphanesini kullanacağım. Sunucu tarafında ise Socket.IO Node.JS chat server kullanacağım

Uygulamanın çalışır halini aşağıdaki videoyu izleyerek inceleyebilirsiniz.

Android Chat uygulamasının özellikleri

1-Kullanıcılar, chat odasına giriş yaptıktan sonra mesajlaşabilmelidir
2-Her kullanıcı, chat odasından ayrıldığında ya da chat odasına katıldığında uygulamada bildirim verilir
3-Kullanıcı mesaj yazarken bildirim yapılır
4- Chat odasına katılan kişilerin sayısı uygulamada gösterilir

Socket.IO kütüphanesi bizlere , tüm tarayıcılar, ağlar ve cihazlar arasında çalışan başarılı bir API sağlıyor. Multiplayer oyunlar veya gerçek zamanlı iletişim için çok uygun olan, inanılmaz sağlam ve yüksek performansa sahiptir.

Uygulama İçin Gerekli Ayarlar

1-AndroidManifest.xml dosyasında internet izni vermek için aşağıdaki kodu eklemeliyiz

<uses-permission android:name="android.permission.INTERNET" />

2-Android Studio Ide ile oluşturduğum projemin app dizinin altındaki build.gradle dosyasını açıyoruz. Dependencies kod bloklarının arasına aşağıdaki kodları yerleştiriyoruz

compile('io.socket:socket.io-client:0.6.2') {
exclude group: 'org.json', module: 'json'
}
compile 'com.android.support:recyclerview-v7:22.2.1'

Uygulamayı Oluşturan Java Kodları

MainActivity.java

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBarActivity;
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainFragment mainFragment=new MainFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.realtabcontent, mainFragment);
transaction.commit();
}
}

Constants.java

public class Constants {
public static final String CHAT_SERVER_URL = "http://chat.socket.io";
}

MainFragment.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.*;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import io.socket.emitter.Emitter;
import io.socket.client.IO;
import io.socket.client.Socket;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class MainFragment extends Fragment {
private static final int REQUEST_LOGIN = 0;
private static final int TYPING_TIMER_LENGTH = 600;
private RecyclerView mMessagesView;
private EditText mInputMessageView;
private List<Message> mMessages = new ArrayList<Message>();
private RecyclerView.Adapter mAdapter;
private boolean mTyping = false;
private Handler mTypingHandler = new Handler();
private String mUsername;
//Socket kullanarak, sunucudaki chat yazılımı ile bağlantı kuruyoruz
private Socket mSocket;
{
try {
//sunucudaki chat yazılımının url set ettik
mSocket = IO.socket(Constants.CHAT_SERVER_URL);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public MainFragment() {
super();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//Mesajları listelicek olan adapter tanımladık
mAdapter = new MessageAdapter(activity, mMessages);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
//Socket sınıfına işlevseliği olan listener metodlarını set ettik
mSocket.on(Socket.EVENT_CONNECT_ERROR, onConnectError);
mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectError);
mSocket.on("new message", onNewMessage);
mSocket.on("user joined", onUserJoined);
mSocket.on("user left", onUserLeft);
mSocket.on("typing", onTyping);
mSocket.on("stop typing", onStopTyping);
//Socket ile bağlantı kurdum..
mSocket.connect();
startSignIn();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onDestroy() {
super.onDestroy();
mSocket.disconnect();
mSocket.off(Socket.EVENT_CONNECT_ERROR, onConnectError);
mSocket.off(Socket.EVENT_CONNECT_TIMEOUT, onConnectError);
mSocket.off("new message", onNewMessage);
mSocket.off("user joined", onUserJoined);
mSocket.off("user left", onUserLeft);
mSocket.off("typing", onTyping);
mSocket.off("stop typing", onStopTyping);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//Mesajların listelenceği adapter tanımladık
mMessagesView = (RecyclerView) view.findViewById(R.id.messages);
mMessagesView.setLayoutManager(new LinearLayoutManager(getActivity()));
mMessagesView.setAdapter(mAdapter);
//Mesajların girildiği edittext arayüz elementini tanımladık  ve mesaj gondermeyi sağlayan metodu cagırdık
mInputMessageView = (EditText) view.findViewById(R.id.message_input);
mInputMessageView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int id, KeyEvent event) {
if (id == R.id.send || id == EditorInfo.IME_NULL) {
attemptSend();
return true;
}
return false;
}
});
mInputMessageView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (null == mUsername) return;
if (!mSocket.connected()) return;
if (!mTyping) {
mTyping = true;
mSocket.emit("typing");
}
mTypingHandler.removeCallbacks(onTypingTimeout);
mTypingHandler.postDelayed(onTypingTimeout, TYPING_TIMER_LENGTH);
}
@Override
public void afterTextChanged(Editable s) {
}
});
ImageButton sendButton = (ImageButton) view.findViewById(R.id.send_button);
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
attemptSend();
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (Activity.RESULT_OK != resultCode) {
getActivity().finish();
return;
}
mUsername = data.getStringExtra("username");
int numUsers = data.getIntExtra("numUsers", 1);
addLog(getResources().getString(R.string.message_welcome));
addParticipantsLog(numUsers);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_main, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_leave) {
leave();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addLog(String message) {
mMessages.add(new Message.Builder(Message.TYPE_LOG)
.message(message).build());
mAdapter.notifyItemInserted(mMessages.size() - 1);
scrollToBottom();
}
//chat sistemine katılan kullanıcı sayısını set eden metod
private void addParticipantsLog(int numUsers) {
addLog(getResources().getQuantityString(R.plurals.message_participants, numUsers, numUsers));
}
//Gönderilen mesajları, mMessages listesine ekleyen metod
private void addMessage(String username, String message) {
mMessages.add(new Message.Builder(Message.TYPE_MESSAGE)
.username(username).message(message).build());
mAdapter.notifyItemInserted(mMessages.size() - 1);
scrollToBottom();
}
private void addTyping(String username) {
mMessages.add(new Message.Builder(Message.TYPE_ACTION)
.username(username).build());
mAdapter.notifyItemInserted(mMessages.size() - 1);
scrollToBottom();
}
private void removeTyping(String username) {
for (int i = mMessages.size() - 1; i >= 0; i--) {
Message message = mMessages.get(i);
if (message.getType() == Message.TYPE_ACTION && message.getUsername().equals(username)) {
mMessages.remove(i);
mAdapter.notifyItemRemoved(i);
}
}
}
//Kullanıcının girdiği mesajı  soket sınıfına set eden metod
private void attemptSend() {
//Kullanıcı adı ve socket ile baglantı kontrol ediyoruz...
if (null == mUsername) return;
if (!mSocket.connected()) return;
mTyping = false;
//Edittext'den mesaj alındı
String message = mInputMessageView.getText().toString().trim();
if (TextUtils.isEmpty(message)) {
mInputMessageView.requestFocus();
return;
}
mInputMessageView.setText("");
addMessage(mUsername, message);
//Kullanıcının girdiği mesajı  soket sınıfına set ettim.
mSocket.emit("new message", message);
}
//Kullanıcı giriş sayfasına yönlendirme yapan metod
private void startSignIn() {
mUsername = null;
Intent intent = new Intent(getActivity(), LoginActivity.class);
startActivityForResult(intent, REQUEST_LOGIN);
}
private void leave() {
mUsername = null;
mSocket.disconnect();
mSocket.connect();
startSignIn();
}
private void scrollToBottom() {
mMessagesView.scrollToPosition(mAdapter.getItemCount() - 1);
}
//Socket ile bağlantığı kurulduğunda bir hatayla karşılasınca uyarı veren metod
private Emitter.Listener onConnectError = new Emitter.Listener() {
@Override
public void call(Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity().getApplicationContext(),
R.string.error_connect, Toast.LENGTH_LONG).show();
}
});
}
};
//Kullanıcıya cevap olarak verilen yeni mesajları socket'den  dinleyen metod
private Emitter.Listener onNewMessage = new Emitter.Listener() {
@Override
public void call(final Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
JSONObject data = (JSONObject) args[0];
String username;
String message;
try {
username = data.getString("username");
message = data.getString("message");
} catch (JSONException e) {
return;
}
removeTyping(username);
//Kullanıcıya cevap olarak verilen yeni mesajları ve cevap veren kullanıcıyı, mesaj listesine eklemesini sağlıyoruz
addMessage(username, message);
}
});
}
};
//chat sistemine giriş yapan kullanıcıların, kullanıcı adlarını ve sayılarını socket'den  dinleyen metod
private Emitter.Listener onUserJoined = new Emitter.Listener() {
@Override
public void call(final Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
JSONObject data = (JSONObject) args[0];
String username;
int numUsers;
try {
username = data.getString("username");
numUsers = data.getInt("numUsers");
} catch (JSONException e) {
return;
}
addLog(getResources().getString(R.string.message_user_joined, username));
//chat sistemine katılan kullanıcı sayısını set ediyoruz...
addParticipantsLog(numUsers);
}
});
}
};
//Chat sisteminden çıkan kullanıcıların bilgilerini socket'den dinleyen metod
private Emitter.Listener onUserLeft = new Emitter.Listener() {
@Override
public void call(final Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
JSONObject data = (JSONObject) args[0];
String username;
int numUsers;
try {
username = data.getString("username");
numUsers = data.getInt("numUsers");
} catch (JSONException e) {
return;
}
addLog(getResources().getString(R.string.message_user_left, username));
addParticipantsLog(numUsers);
removeTyping(username);
}
});
}
};
//Hangi kullanıcının yazı yazdığını socket'den dinleyen metod
//Kullanıcı yazı yazarken; örneğin Tugba kullanıcısı yazıyor gibi açıklaması yapabilmek için kullanılıyor
private Emitter.Listener onTyping = new Emitter.Listener() {
@Override
public void call(final Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
JSONObject data = (JSONObject) args[0];
String username;
try {
username = data.getString("username");
} catch (JSONException e) {
return;
}
addTyping(username);
}
});
}
};
private Emitter.Listener onStopTyping = new Emitter.Listener() {
@Override
public void call(final Object... args) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
JSONObject data = (JSONObject) args[0];
String username;
try {
username = data.getString("username");
} catch (JSONException e) {
return;
}
removeTyping(username);
}
});
}
};
private Runnable onTypingTimeout = new Runnable() {
@Override
public void run() {
if (!mTyping) return;
mTyping = false;
mSocket.emit("stop typing");
}
};
}

LoginActivity.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import io.socket.emitter.Emitter;
import io.socket.client.IO;
import io.socket.client.Socket;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
public class LoginActivity extends Activity {
private EditText mUsernameView;
private String mUsername;
//Socket sınıfını kullanarak, sunucudaki chat yazılımı ile bağlantı kuruyoruz
private Socket mSocket;
{
try {
mSocket = IO.socket(Constants.CHAT_SERVER_URL);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//Kullanıcı chat sistemine giriş yapabilmesi için kullanıcı adını alıyoruz
mUsernameView = (EditText) findViewById(R.id.username_input);
mUsernameView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button signInButton = (Button) findViewById(R.id.sign_in_button);
signInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
//Socket sınıfına,kullanıcı login işlemini yapan metodu set ettim
mSocket.on("login", onLogin);
}
@Override
protected void onDestroy() {
super.onDestroy();
mSocket.off("login", onLogin);
}
/**
*Kullanıcı chat sistemine giriş yapabilmesi için, edittextden alınan değer kontrol edilten sonra
* kullanıcıyı socket sınıfına ekleyen metod
*/
private void attemptLogin() {
mUsernameView.setError(null);
String username = mUsernameView.getText().toString().trim();
// Kulanıcı adı kontrol ediliyor
if (TextUtils.isEmpty(username)) {
mUsernameView.setError(getString(R.string.error_field_required));
mUsernameView.requestFocus();
return;
}
mUsername = username;
//Kullanıcı adı, socket'e ekleniyor
mSocket.emit("add user", username);
}
/**
* Socket sınıfını dinleyerek, login işlemni yapan metod
*/
private Emitter.Listener onLogin = new Emitter.Listener() {
@Override
public void call(Object... args) {
JSONObject data = (JSONObject) args[0];
//numUsers; chat sisteminde kaç kişinin login olduğunu döndüren sayıdır.Yani chat sistemini kullanan
//katılımcıları belirten sayıdır
int numUsers;
try {
numUsers = data.getInt("numUsers");
} catch (JSONException e) {
return;
}
Intent intent = new Intent();
intent.putExtra("username", mUsername);
intent.putExtra("numUsers", numUsers);
setResult(RESULT_OK, intent);
finish();
}
};
}

Message.java

public class Message {
public static final int TYPE_MESSAGE = 0;
public static final int TYPE_LOG = 1;
public static final int TYPE_ACTION = 2;
private int mType;
private String mMessage;
private String mUsername;
private Message() {}
public int getType() {
return mType;
};
public String getMessage() {
return mMessage;
};
public String getUsername() {
return mUsername;
};
public static class Builder {
private final int mType;
private String mUsername;
private String mMessage;
public Builder(int type) {
mType = type;
}
public Builder username(String username) {
mUsername = username;
return this;
}
public Builder message(String message) {
mMessage = message;
return this;
}
public Message build() {
Message message = new Message();
message.mType = mType;
message.mUsername = mUsername;
message.mMessage = mMessage;
return message;
}
}
}

MessageAdapter.java

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder> {
private List<Message> mMessages;
private int[] mUsernameColors;
public MessageAdapter(Context context, List<Message> messages) {
mMessages = messages;
mUsernameColors = context.getResources().getIntArray(R.array.username_colors);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int layout = -1;
switch (viewType) {
case Message.TYPE_MESSAGE:
layout = R.layout.item_message;
break;
case Message.TYPE_LOG:
layout = R.layout.item_log;
break;
case Message.TYPE_ACTION:
layout = R.layout.item_action;
break;
}
View v = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
Message message = mMessages.get(position);
viewHolder.setMessage(message.getMessage());
viewHolder.setUsername(message.getUsername());
}
@Override
public int getItemCount() {
return mMessages.size();
}
@Override
public int getItemViewType(int position) {
return mMessages.get(position).getType();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView mUsernameView;
private TextView mMessageView;
public ViewHolder(View itemView) {
super(itemView);
mUsernameView = (TextView) itemView.findViewById(R.id.username);
mMessageView = (TextView) itemView.findViewById(R.id.message);
}
public void setUsername(String username) {
if (null == mUsernameView) return;
mUsernameView.setText(username);
mUsernameView.setTextColor(getUsernameColor(username));
}
public void setMessage(String message) {
if (null == mMessageView) return;
mMessageView.setText(message);
}
private int getUsernameColor(String username) {
int hash = 7;
for (int i = 0, len = username.length(); i < len; i++) {
hash = username.codePointAt(i) + (hash << 5) - hash;
}
int index = Math.abs(hash % mUsernameColors.length);
return mUsernameColors[index];
}
}
}

Uygulamanun arayüz xml kodlarınıda github’a eklediğim projeden incelerseniz sizin için daha faydalı olacaktır.

Son olarak ufak bir not: Bu yukarıda anlattığım projemin kodlarını indirmek isterseniz; yapmanız gereken tek şey aşağıya koyduğum KODLARI İNDİR resmine tıklamak.

download

Kaynak
1-https://github.com/nkzawa/socket.io-android-chat

Kategori Genel
Etiketler