준비사항
본격적인 앱 개발에 앞서 필요한 프로젝트 구조와 필수 라이브러리, 그리고 초기 설정 사항을 확인합니다.
프로젝트 폴더 구조
아래는 Android 채팅 샘플 프로젝트의 주요 폴더 구조입니다.
프로젝트 ( app 폴더 외 파일은 제외 )
├─build.gradle
├─libs
│ ├─android-support-v4.jar
│ └─BGAPhotoPicker.aar
├─src.test.java.com.vchatcloud.androidsample.ExampleUnitTest.java
├─src.androidTest.java.com.vchatcloud.androidsample.ExampleInstrumentedTest.java
└─src.main
├─AndroidManifest.xml
├─ic_launcher-playstore.png
├─res
│ ├─color
│ ├─drawable
│ ├─drawable-v24
│ ├─layout
│ ├─mipmap-anydpi-v26
│ ├─mipmap-hdpi
│ ├─mipmap-mdpi
│ ├─mipmap-xhdpi
│ ├─mipmap-xxhdpi
│ ├─mipmap-xxxhdpi
│ ├─values
│ └─xml
└─java.com.vchatcloud.androidsample
├─adapter
│ ├─ChatListAdapter.java
│ ├─EmojiAdapter.java
│ └─HorizontalListView.java
├─fragment
│ └─EmojiFragment.java
├─photoView
│ ├─HackyViewPager.java
│ └─ViewPagerActivity.java
├─resourse
│ ├─Constant.java
│ ├─EmojiRescourse.java
│ └─ProfileRescource.java
├─viewpagerindicator
│ ├─CirclePageIndicator.java
│ └─PageIndicator.java
├─BackKeyHandler.java
├─ChatActivity.java
├─ImageEdit.java
├─LoadingActivity.java
├─MainActivity.java
├─Message.java
├─PreferenceManager.java
└─ProgressRequestBody.java
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
필수 변경 사항
샘플 앱을 실행하기 전에 반드시 channelKey
값을 사용자의 채팅방 정보로 변경해야 합니다. 아래 단계를 따라 변경해 주세요.
- VChatCloud CMS에서 생성한 채팅방의
Channel Key
를 복사합니다. - 제공된 Android 샘플 앱 소스 코드를 다운로드하고 압축을 해제합니다.
- 압축 해제된 프로젝트 폴더에서
app\src\main\java\com\vchatcloud\androidsample\resourse
경로로 이동하여Constant.java
파일을 텍스트 편집기로 엽니다. Constant.java
파일 내에서ROOM_ID
라는 이름의String
변수 값을 1번에서 복사한 채널 키로 붙여넣습니다.
public class Constant {
public static final String ROOM_ID = "YOUR_CHANNEL_KEY"; // 이 부분을 복사한 채널 키로 변경하세요.
public static final String BASE = "https://vchatcloud.com";
public static final String SAVEFILE = "/api/openapi/saveFile";
public static final String LOADFILE = "/api/openapi/loadFile";
}
2
3
4
5
6
VChatCloud 객체
VChatCloud 클래스
VChatCloud
클래스의 인스턴스는 싱글톤 패턴으로 제공됩니다. 앱 내에서 다음과 같이 호출하여 사용할 수 있습니다.
VChatCloud.getInstance()
channel
메소드
channel
은 VChatCloud
인스턴스에 구현된 주요 메소드입니다. 이 메소드를 통해 채팅방에 참여하고, 메시지 송수신 및 다양한 채팅 관련 이벤트를 제어할 수 있습니다. 아래 코드는 채팅방 참여 과정을 보여주며, 메시지 전송, 공지 등 다른 이벤트 관련 자세한 내용은 메시지 문서를 참고해 주세요.
ChannelOptions options = new ChannelOptions();
options.setChannelKey(room_id);
options.setClientKey(deviceUuid);
options.setNickName(nick_name);
options.setUserInfo(userInfo);
Channel channel = VChatCloud.getInstance().joinChannel(options, new JoinChannelCallback() {
public void callback(JSONArray history, VChatCloudException e) {
super.callback(history, e);
int historySize = history.size() -1;
for (; historySize >= 0; historySize--) {
Log.d("히스토리", "" + history.get(historySize) );
}
Log.d("히스토리", "룸 시작");
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ChannelOptions
파라미터~
값 | 식별자 | 설명 |
---|---|---|
ChannelKey | String | VChatCloud CMS에서 발급받은 채팅방 Channel Key |
ClientKey | String | 접속 단말 설정 고유키 |
NickName | String | 채팅방에서 사용할 사용자 닉네임 |
UserInfo | JSONObject | 사용자 프로필 정보 (JSON 형식) |
결과 값
VChatCloudException
정보
값 식별자 설명 code String 에러 코드 type String 에러 타입 message String 에러 메시지 history
아이템 정보
값 식별자 설명 logType String 로그 타입 roomId String 채팅방 Channel Key clientKey String 접속 단말 설정 고유키 message String 메시지 내용 mimeType String 메시지 형태 (text: 일반텍스트, emoji: 이모지) messageType String 빈값이면 일반 메시지, 공지일경우 : "notice" nickName String 채팅방 입장 유저의 닉네임 date String 메시지 발송 시간 grade String 사용자 등급
주요 코드 작성
샘플 앱의 주요 기능 (채팅방 입장, 메시지 전송, 귓속말, 공지, 추방, 글쓰기 제한) 관련 Java 코드 스니펫입니다. 앱 실행 시 로그인 팝업을 통해 채팅방 번호 및 닉네임을 입력받는 방식으로 구현되어 있으니 참고해 주세요.
로그인 창
MainActivity.java
파일의 로그인 관련 코드입니다. 닉네임과 프로필 정보를 입력받아 ChatActivity
로 전달하며, 알림 권한을 요청하는 로직이 포함되어 있습니다.
// ChatActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
roomId = Constant.ROOM_ID;
// 메인 화면 컨텐츠 셋팅
setContentView(R.layout.main);
// 아이콘 리스트 셋팅(아이콘)
setUpHorizontalListView();
GradientDrawable myGrad = (GradientDrawable) getApplicationContext().getDrawable(R.drawable.profile_img_back_ground);
DisplayMetrics outMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
appMetrics = outMetrics;
// 알림 셋팅
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.deleteNotificationChannel(String.valueOf(R.string.channel_id));
// NotificationManager manager = getApplicationContext().getSystemService(NotificationManager.class);
notificationChannel = new NotificationChannel(String.valueOf(R.string.channel_id), String.valueOf(R.string.channel_name), NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(String.valueOf(R.string.channel_description));
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.BLUE);
notificationChannel.enableVibration(true);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(notificationChannel);
}
// 기본 액션 셋팅
et_nick = findViewById(R.id.et_nick);
btn_send = findViewById(R.id.btn_send);
et_nick.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
buttonFlag = true;
if (start < 1 && count < 1) {
if (charSequence.length() < 1) {
buttonFlag = false;
et_nick.setError("사용자 이름을 한글자 이상 입력해 주세요.");
}
}
}
@Override
public void afterTextChanged(Editable editable) {
}
});
// 텍스트 완료버튼 액션
et_nick.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) { // 완료버튼
btn_send.callOnClick();
}
return true;
}
});
// 확인 버튼 액션
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (buttonFlag && profile_select != 0) {
Intent intent = new Intent(MainActivity.this, ChatActivity.class);
intent.putExtra("room", roomId); // 룸 ID
intent.putExtra("started", started); // 시작 알림
intent.putExtra("nick", et_nick.getText().toString()); // 닉네임 정보
intent.putExtra("nickIcon", profile_select); // 닉네임 아이콘 정보
startActivity(intent);
} else {
// 유효성 불능
if (!buttonFlag) {
// 아이디가 오류
Toast toast = Toast.makeText(MainActivity.this, "사용자 아이디를 입력해 주세요.", Toast.LENGTH_SHORT);
toast.show();
if (et_nick.requestFocus()) {
// 키보드 강제로 올리기
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
} else if (profile_select == 0) {
Toast toast = Toast.makeText(MainActivity.this, "캐릭터를 선택해 주세요.", Toast.LENGTH_SHORT);
toast.show();
}
}
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
채팅영역에 메시지 표시
ChatActivity.java
파일의 messageExposure
메소드는 수신된 메시지를 화면에 표시하는 역할을 합니다. 앱이 백그라운드 상태일 때 알림을 보내는 기능과 RecyclerView
어댑터를 통해 메시지를 UI에 업데이트하는 로직이 포함되어 있습니다.
// ChatActivity.java
public void messageExposure(Message message, final boolean sendScroll) {
int profile_index = 0;
if (ChatActivity.notificationFlag && (message.getMimeType().equalsIgnoreCase("text"))) {
++msgId;
// PushCallDisplay();
NotificationCompat.Builder builder = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new NotificationCompat.Builder(this, String.valueOf(R.string.channel_id));
}else {
builder = new NotificationCompat.Builder(this);
}
Intent resultIntent = new Intent(this, ChatActivity.class);
resultIntent.putExtra("scroll","true");
PendingIntent pendingIntent = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
}
} catch (Exception e) {
e.printStackTrace();
}
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(),defaultSoundUri);
ringtone.play();
if (message.getMessageType() != null) {
profile_index = Integer.parseInt((String) message.getMessageType().get("profile")) - 1;
}
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), MainActivity.ProfileInfo(profile_index));
Log.d(TAG, message.getClientKey());
builder
.setContentTitle(message.getNickName())
.setContentText(message.getMessage())
.setSmallIcon(R.drawable.vchat_logo)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.notificationColor))
.setLargeIcon(bitmap) // 사용자 아이콘?
// .setOnlyAlertOnce(true)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND)
.setContentIntent(pendingIntent);
Notification notification = builder.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
String cId = message.getClientKey();
msgId = 0;
for (int i = 0; i < cId.length(); i++) {
msgId = (cId.charAt(i) - 64) + msgId;
}
notificationManager.notify(msgId, notification);
}
// 이모지 처리
if ("emoji_img".equalsIgnoreCase(message.getMimeType())) {
message.setType("emoji_img");
} else if ("file".equalsIgnoreCase(message.getMimeType())) {
message.setType("file");
}
Log.d(TAG, message.toString());
$Adapter.addMsg(message);
runOnUiThread(new Runnable() {
@Override
public void run() {
$Adapter.notifyDataSetChanged();
if (scrollstate) {
$RecyclerView.smoothScrollToPosition($Adapter.getItemCount());
}
if (sendScroll) {
$RecyclerView.smoothScrollToPosition($Adapter.getItemCount());
}
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
이모티콘 창 노출
ChatActivity.java
파일에서 이모티콘 프래그먼트를 표시하고 닫는 로직입니다. 이모티콘 버튼 클릭 시 프래그먼트 트랜잭션을 사용하여 화면에 표시/숨김 처리합니다.
// ChatActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
$EmojiButton = (ImageButton) findViewById(R.id.button_open_channel_chat_emoji);
$EmojiButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (emojiFlag) {
closeEmoji();
} else {
openEmoji();
}
}
});
}
private void openEmoji() {
emojiFlag = true;
$EmojiButton.setImageResource(R.drawable.ic_round_mood_24_active);
transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.frameLayout, emojiFragment).commitAllowingStateLoss();
}
private void closeEmoji() {
emojiFlag = false;
$EmojiButton.setImageResource(R.drawable.ic_round_mood_24);
transaction = fragmentManager.beginTransaction();
transaction.remove(emojiFragment).commitAllowingStateLoss();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
귓속말 팝업창
ChatActivity.java
와 ChatListAdapter.java
파일에서 메시지 롱 클릭 이벤트를 처리하여 귓속말을 보낼 수 있는 AlertDialog
를 표시하는 로직입니다.
// ChatActivity.java
$Adapter.setOnItemLongClickListener(new ChatListAdapter.OnItemLongClickListener() {
@Override
public void onUserMessageItemLongClick(Message message, int position) {
Log.d("message >> ", message.toString());
AlertDialog.Builder ad = new AlertDialog.Builder(ChatActivity.this);
final EditText et = new EditText(ChatActivity.this);
ad.setTitle(message.getNickName() + " 님에게 귓속말 보내기");
ad.setMessage("");
ad.setView(et);
ad.setPositiveButton("전송", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "Yes Btn Click");
// Text 값 받아서 로그 남기기
String value = et.getText().toString();
Log.d(TAG, value);
JSONObject json = new JSONObject();
json.put("receivedClientKey", message.getClientKey());
json.put("message", value);
json.put("mimeType", "text");
channel.sendWhisper(json, new ChannelCallback() {
@Override
public void callback(Object o, VChatCloudException e) {
if (e != null) {
Log.d(TAG,"메시지 전송오류");
}
$RecyclerView.smoothScrollToPosition($Adapter.getItemCount());
}
});
dialog.dismiss(); //닫기
}
});
// 취소 버튼 설정
ad.setNegativeButton("취소", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG,"No Btn Click");
dialog.dismiss(); //닫기
// Event
}
});
ad.show();
}
});
// ChatListAdapter.java
if (longClickListener != null) {
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
longClickListener.onUserMessageItemLongClick(message, position);
return true;
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59