2026/02/16

오늘의 이야기










오늘은 barcode 인식을 이용한 앱 구현에 대한 정리를 해 볼까 한다.    이 기능 구현의 시작은 어느 티비에서 방송했던, 어쩌다 사장  이라는 방송에서 출연진들이  원래 가게 주인이 적어든 가격표를 찾아가면서 판매를 하는 것을 보고, 단순한 바코드 인식을 통해 pos 을 구현해 볼 수 있을 까 하는 생각에서 출발한다.


dependencies {

.....
implementation 'com.journeyapps:zxing-android-embedded:4.0.0'
.....
}

먼저 gradle 파일에 implementation 을 추가한다. 구글링을 통해서 발견한 barcode 인식을 위한 준비라고나 할까 ?


그리고 사용하는 방법은 activity 에서 호출해 주기만 하면 되고, onActivityResult 에서 응답으로 바코드 인식된 값을 돌려 받아 처리 하면 된다. 다음 source code 을 보자.


........

import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

IntentIntegrator integrator;

......

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

.....

binding.btnQRscan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
integrator = new IntentIntegrator(MainActivity.this);
//바코드 안의 텍스트
integrator.setPrompt(getString(R.string.msgViewBarCode));
//바코드 인식시 소리 여부
integrator.setBeepEnabled(false);
integrator.setBarcodeImageEnabled(true);
integrator.setCaptureActivity(CaptureActivity.class);
//바코드 스캐너 시작
integrator.initiateScan();
}
});

......

onDispData(strMode, productNameValue);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null){
if(result.getContents() == null){
// 바코드 인식이 안된 경우의 처리
doHandwork("") ;
}else{
//barcode 코드를 읽어서 EditText에 입력해줍니다.
binding.et.setText(result.getContents());

.....

KakaoToast.makeToast(this, "Scanned: " + result.getContents(), Toast.LENGTH_SHORT).show();
}
}else{
super.onActivityResult(requestCode, resultCode, data);
}
}

.....

}

전체 소스 중에서 바코드 처리 부분만 남겼는데,  버튼 클릭으로 barcode 인식 activity 을 호출 하고 응답이 돌아오면 그 값을 edittext 에 넣어주는 것으로 처리를 남겼다.  source 에서 보면integrator.setPrompt(getString(R.string.msgViewBarCode)); 을 통해서 바코드 인식하는 화면에 메시지를 넣어준 것으로 볼 수 있을 것이다.




이런 예시 화면 처럼 화면에 메시지를 보여줌으로 해서 사용자에게 동작을 인식시키는 기능도 구현이 가능 하다.


바코드 인식부의 중간에는 기준선이 빨간색으로 표시 되어 있어 사용이 수월하게 진행 될 수 있다.


source code 을 보면 barcode 사용을 위해서  integrator.setCaptureActivity(CaptureActivity.class); 처럼 activity을 호출 하는데, 여기 명시된 CaptureActivity 는 내가 작성한 것이 아니고, 


(2021.09.09 수정) gradle 에서 선언한 com.journeyapps:zxing-android-embedded:4.0.0 이 안에 들어 있는 class 인데, 이것을 다시 정의하는 클래스만 을 선언 하면 된다.


public class CaptureActivity extends com.journeyapps.barcodescanner.CaptureActivity {

}

완전 깔금 하지 않은가 ...


 


 


 


 


 




다른 화면들을 보면 이렇게 구동을 확인해 볼 수 있다.


이렇게 해서 구현된 앱은 참고로 


https://play.google.com/store/apps/details?id=com.billcoreatech.boss0426 



 


우연히 사장 - Google Play 앱


우연히 사장이 되신 분들의 건투를 빌며.


play.google.com




 


playstore 에서도 확인해 볼 수 있다.


 





반응형
























오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기










아무래도 휴대폰에 앱을 개발하는 일이다 보니, 문자 전송 같은 기본 기능을 이용하는 앱을 개발하는 일이 많기는 하다.  요새는 카카오톡등을 이용하거나, FCM 등을 이용해서 알림을 보내는 기능등을 구현해 보지만, 예전 방식 처럼 SMS 을 전송해 보는 것도 오랜만 이기는 하다.


구글이 계정 정책등으로 인해 앱을 등록할 때 기본앱으로 사용할 수 없는 SMS 앱은 등록에 무리가 있다. 그래서 그냥 개인적으로 사용하기 위해서 기본앱 기능은 아니지만, 필요에 의한 문자 전송을 구현하는 앱을 만들어 볼 까 싶다.


먼저 앱을 구동하기 위해서는 Manifest 파일에 권한 설정을 등록해 준다. 


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smssend0823">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SMSSend0823">

......

</application>

</manifest>

SMS 발송 권한을 설정했으니, 앱을 실행하면서 실제 동작이 발생 하기 전에 사용자에게 권한을 획득 하는 코드를 activity 에 넣어 보자.


import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

import com.example.smssend0823.Database.DBHandler;
import com.example.smssend0823.Database.MsgTargetBean;
import com.example.smssend0823.Database.SendMessageAdapter;
import com.example.smssend0823.Database.SendMessageBean;
import com.example.smssend0823.Utils.OnBackPressedListener;
import com.example.smssend0823.databinding.FragmentMsgselectBinding;

import java.util.ArrayList;

public class MsgSelectFragment extends Fragment implements OnBackPressedListener {

private static final String TAG = "MsgSelectFragment";
private FragmentMsgselectBinding binding;
ArrayList<SendMessageBean> beans;
SendMessageAdapter adapter;
DBHandler dbHandler ;
String phoneNo ;
String message ;
private static final int PERMISSION_RQST_SEND = 101;

public static Fragment newInstance() {
MsgSelectFragment fragment = new MsgSelectFragment();
return fragment;
}

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

binding = FragmentMsgselectBinding.inflate(inflater, container, false);
return binding.getRoot();

}

public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

beans = new ArrayList<>();
doDisplaySendMessage();
binding.listSendMessage.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int iCnt = 0 ;
for(int i=0; i < beans.size() ; i++) {
if (beans.get(i).isSelectedMsg()) iCnt++;
}
if (iCnt > 0) {
beans.get(position).setSelectedMsg(false);
adapter.notifyDataSetChanged(beans);
Toast.makeText(getContext(), iCnt +"개 이상 선택할 수 없습니다.", Toast.LENGTH_SHORT).show();
return;
}
beans.get(position).setSelectedMsg(!beans.get(position).isSelectedMsg());
adapter.notifyDataSetChanged(beans);
}
});

binding.btnSendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = 0 ; int iCnt = 0 ;
for(int i=0; i < beans.size() ; i++) {
if (beans.get(i).isSelectedMsg()) {
position = i;
iCnt++;
break;
};
}
if (iCnt == 0) {
Toast.makeText(getContext(), "전송할 메시지를 선택하세요.", Toast.LENGTH_SHORT).show();
return;
}
message = beans.get(position).getSendMessage() ;
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("메시지 전송")
.setMessage("[" + beans.get(position).getSendMessage() + "] 로 전송할까요?")
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dbHandler = DBHandler.open(getContext());
Cursor rs = dbHandler.selectAll();
while(rs.moveToNext()) {
MsgTargetBean msgTargetBean = new MsgTargetBean();
msgTargetBean.setId(rs.getInt(rs.getColumnIndex("_id")));
msgTargetBean.setSendSeqNo(rs.getString(rs.getColumnIndex("sendSeqNo")));
msgTargetBean.setReceivePhoneNum(rs.getString(rs.getColumnIndex("receivePhoneNum")));
msgTargetBean.setSendMessage(rs.getString(rs.getColumnIndex("sendMessage")));
msgTargetBean.setSendTy(rs.getString(rs.getColumnIndex("sendTy")));
if("S".equals(msgTargetBean.getSendTy())) {
phoneNo = msgTargetBean.getReceivePhoneNum();
if (sendSMSMessage()) {
msgTargetBean.setSendTy("Y");
msgTargetBean.setSendMessage(message);
dbHandler.update(msgTargetBean);
}
}
}
dbHandler.close();
}
})
.setNegativeButton("취소", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
});
AlertDialog dialog = builder.create();
dialog.show();
}
});
}

private void doDisplaySendMessage() {
beans.clear();
dbHandler = DBHandler.open(getContext());
Cursor rs = dbHandler.selectMsgAll();
while (rs.moveToNext()) {
SendMessageBean bean = new SendMessageBean();
bean.setSendMessage(rs.getString(rs.getColumnIndex("messageString")));
beans.add(bean);
}
adapter = new SendMessageAdapter(beans, getContext());
adapter.notifyDataSetChanged(beans);
binding.listSendMessage.setAdapter(adapter);
}

protected boolean sendSMSMessage() {
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getContext(), "SMS 전송 권한에 대한 허가가 필요합니다.", Toast.LENGTH_SHORT).show();
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),Manifest.permission.SEND_SMS)) {
Log.e(TAG, "shouldShowRequestPermissionRationale");
}
else { ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.SEND_SMS}, PERMISSION_RQST_SEND);
Log.e(TAG, "requestPermissions");
}
} else {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, message, null, null);
Toast.makeText(getContext(), "메시지가 전송 되었습니다.",Toast.LENGTH_LONG).show();
return true ;
}
return false;
}
//Now once the permission is there or not would be checked
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_RQST_SEND: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

} else {Toast.makeText(getContext(), "SMS 발송이 되지 않았습니다. 잠시 뒤에 다시 시도 하세요.", Toast.LENGTH_LONG).show();
return;
}
}
}
}

@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}

@Override
public void onBackPressed() {
getParentFragmentManager().beginTransaction()
.replace(R.id.container, MsgSendListFragment.newInstance())
.commitNow();
}
}

 


source code 의 예시와 같이 sendSMSMessage() 함수에는 메시지 발송전에 권한 획득 여부를 확인하고, 권한이 없다면 사용자에게 권한 허가를 요청하는 알림을 띄워 권한을 획득 하게 된다.


다만, 이런 기능을 앱으로 이용하려면 playstore 에는 등록에 제한이 있으므로 다른 스토어를 이용하여야 한다.


SMS을 보내는 다른 방법은 권한 없이 사용할 수 있는 SMS Retriever API 을 활용하는 방법이 있다고 할 수 있는데, 실제 앱을 개발하다 보면 불편한 사항이 생기게 되어 있다.


https://developers.google.com/identity/sms-retriever/overview



 


SMS Retriever API를 사용한 자동 SMS 확인  |  SMS Verification APIs


이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English SMS Retriever API를 사용한 자동 SMS 확인 SMS Retriever API를 사용하면 사용자가 인증 코드를 수동으로 입력 할 필요없이 추가 앱 권


developers.google.com




참고해서 보면 좋을 것 깥다.


 





반응형
























오늘의 이야기




앱을 만들다 보면 listview 혹인 recyclelistview 등을 사용할 일이 있다.  그런데, 그 안에 있는 item 으로 checkbox, button 등을 넣어 화면을 구성해 보면 (focus 가 도달하는 것들) listview 의 item click 이 제어되지 않고 그 안에 있는 checkbox나 button 등이 클릭 되는 현상을 보인다.




 이럴 떄 어떻게 하면 listview 의 item 으로 제어를 옮길 수 있는 것인가에 대한 고민이 생긴다.


구글신에게 물어보고 찾아보는 방법이 있기는 하지만, 그럴 시간이면 간단하게...


 


        <CheckBox
android:id="@+id/ckSendSeqNo"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8"
android:focusable="false"
android:clickable="false" />

 


checkbox 나 button 에게 focus가 가지 못하게 false 처리를 해 주는 것이다. click 도 못하게 false 을 해 준다.


그리고 listview 의 setOnItemClickListener 을 이용해서 


데이터 값을 조정해 주고 adapter 에서 checkbox 의 값을 설정해 주는 방식으로 처리를 해본다.


 


 


 


 


        binding.listSendMessage.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int iCnt = 0 ;
for(int i=0; i < beans.size() ; i++) {
if (beans.get(i).isSelectedMsg()) iCnt++;
}
if (iCnt > 0) {
beans.get(position).setSelectedMsg(false);
adapter.notifyDataSetChanged(beans);
Toast.makeText(getContext(), iCnt +"개 이상 선택할 수 없습니다.", Toast.LENGTH_SHORT).show();
return;
}
beans.get(position).setSelectedMsg(!beans.get(position).isSelectedMsg());
adapter.notifyDataSetChanged(beans);
}
});

beans.get(position).setSelectedMsg(false); 이렇게 false 로 하던지 아니면 true 로 설정을 한 다음...


import android.content.Context;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.TextView;

import com.example.smssend0823.R;
import com.example.smssend0823.databinding.DataitemBinding;

import java.util.ArrayList;

public class MsgSendAdapter extends BaseAdapter {

DataitemBinding binding ;
ArrayList<MsgTargetBean> msgTargetBeans ;
LayoutInflater inflater;
Context context ;
int nListCnt ;

public MsgSendAdapter(ArrayList<MsgTargetBean> odata, Context context) {
this.msgTargetBeans = odata ;
this.context = context ;
this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

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

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

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

public void notifyDataSetChanged(ArrayList<MsgTargetBean> odata) {
this.msgTargetBeans = odata ;
nListCnt = msgTargetBeans.size();
this.notifyDataSetChanged();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
binding = DataitemBinding.inflate(inflater);
CustomViewHolder holder ;
if (convertView == null) {
convertView = binding.getRoot() ;
holder = new CustomViewHolder();
holder.ckSendSeqNo = binding.ckSendSeqNo ;
holder.txtSendMessage = binding.txtSendMessage ;
holder.txtReceivedPhoneNum = binding.txtReceivedPhoneNum ;
holder.txtSendTy = binding.txtSendTy ;
convertView.setTag(holder);
} else {
holder = (CustomViewHolder) convertView.getTag();
}

holder.ckSendSeqNo.setChecked(msgTargetBeans.get(position).getSelectSend());
holder.ckSendSeqNo.setText(msgTargetBeans.get(position).getSendSeqNo());
holder.txtSendMessage.setText(msgTargetBeans.get(position).getSendMessage());
holder.txtReceivedPhoneNum.setText(msgTargetBeans.get(position).getReceivePhoneNum());
if ("Y".equals(msgTargetBeans.get(position).getSendTy())) {
holder.txtSendTy.setText("전송완료");
holder.txtSendTy.setBackgroundColor(context.getColor(R.color.softBlue));
holder.txtSendTy.setTextColor(context.getColor(R.color.white));
} else {
holder.txtSendTy.setText("");
holder.txtSendTy.setBackgroundColor(context.getColor(R.color.white));
holder.txtSendTy.setTextColor(context.getColor(R.color.white));
}
return convertView;
}

public class CustomViewHolder {
CheckBox ckSendSeqNo ;
TextView txtSendTy ;
TextView txtSendMessage ;
TextView txtReceivedPhoneNum ;
}
}

holder.ckSendSeqNo.setChecked(msgTargetBeans.get(position).getSelectSend()); 처럼 실행해 주면 그것으로 끝.


adapter 을 호출하는 전체 소스는 아래와 같다.


import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

import com.example.smssend0823.Database.DBHandler;
import com.example.smssend0823.Database.MsgTargetBean;
import com.example.smssend0823.Database.SendMessageAdapter;
import com.example.smssend0823.Database.SendMessageBean;
import com.example.smssend0823.Utils.OnBackPressedListener;
import com.example.smssend0823.databinding.FragmentMsgselectBinding;

import java.util.ArrayList;

public class MsgSelectFragment extends Fragment implements OnBackPressedListener {

private static final String TAG = "MsgSelectFragment";
private FragmentMsgselectBinding binding;
ArrayList<SendMessageBean> beans;
SendMessageAdapter adapter;
DBHandler dbHandler ;
String phoneNo ;
String message ;
private static final int PERMISSION_RQST_SEND = 101;

public static Fragment newInstance() {
MsgSelectFragment fragment = new MsgSelectFragment();
return fragment;
}

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

binding = FragmentMsgselectBinding.inflate(inflater, container, false);
return binding.getRoot();

}

public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

beans = new ArrayList<>();
doDisplaySendMessage();
binding.listSendMessage.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int iCnt = 0 ;
for(int i=0; i < beans.size() ; i++) {
if (beans.get(i).isSelectedMsg()) iCnt++;
}
if (iCnt > 0) {
beans.get(position).setSelectedMsg(false);
adapter.notifyDataSetChanged(beans);
Toast.makeText(getContext(), iCnt +"개 이상 선택할 수 없습니다.", Toast.LENGTH_SHORT).show();
return;
}
beans.get(position).setSelectedMsg(!beans.get(position).isSelectedMsg());
adapter.notifyDataSetChanged(beans);
}
});

binding.btnSendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = 0 ; int iCnt = 0 ;
for(int i=0; i < beans.size() ; i++) {
if (beans.get(i).isSelectedMsg()) {
position = i;
iCnt++;
break;
};
}
if (iCnt == 0) {
Toast.makeText(getContext(), "전송할 메시지를 선택하세요.", Toast.LENGTH_SHORT).show();
return;
}
message = beans.get(position).getSendMessage() ;
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("메시지 전송")
.setMessage("[" + beans.get(position).getSendMessage() + "] 로 전송할까요?")
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dbHandler = DBHandler.open(getContext());
Cursor rs = dbHandler.selectAll();
while(rs.moveToNext()) {
MsgTargetBean msgTargetBean = new MsgTargetBean();
msgTargetBean.setId(rs.getInt(rs.getColumnIndex("_id")));
msgTargetBean.setSendSeqNo(rs.getString(rs.getColumnIndex("sendSeqNo")));
msgTargetBean.setReceivePhoneNum(rs.getString(rs.getColumnIndex("receivePhoneNum")));
msgTargetBean.setSendMessage(rs.getString(rs.getColumnIndex("sendMessage")));
msgTargetBean.setSendTy(rs.getString(rs.getColumnIndex("sendTy")));
if("S".equals(msgTargetBean.getSendTy())) {
phoneNo = msgTargetBean.getReceivePhoneNum();
if (sendSMSMessage()) {
msgTargetBean.setSendTy("Y");
msgTargetBean.setSendMessage(message);
dbHandler.update(msgTargetBean);
}
}
}
dbHandler.close();
}
})
.setNegativeButton("취소", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
});
AlertDialog dialog = builder.create();
dialog.show();
}
});
}

private void doDisplaySendMessage() {
beans.clear();
dbHandler = DBHandler.open(getContext());
Cursor rs = dbHandler.selectMsgAll();
while (rs.moveToNext()) {
SendMessageBean bean = new SendMessageBean();
bean.setSendMessage(rs.getString(rs.getColumnIndex("messageString")));
beans.add(bean);
}
adapter = new SendMessageAdapter(beans, getContext());
adapter.notifyDataSetChanged(beans);
binding.listSendMessage.setAdapter(adapter);
}

protected boolean sendSMSMessage() {
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getContext(), "SMS 전송 권한에 대한 허가가 필요합니다.", Toast.LENGTH_SHORT).show();
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),Manifest.permission.SEND_SMS)) {
Log.e(TAG, "shouldShowRequestPermissionRationale");
}
else { ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.SEND_SMS}, PERMISSION_RQST_SEND);
Log.e(TAG, "requestPermissions");
}
} else {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, message, null, null);
Toast.makeText(getContext(), "메시지가 전송 되었습니다.",Toast.LENGTH_LONG).show();
return true ;
}
return false;
}
//Now once the permission is there or not would be checked
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_RQST_SEND: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

} else {Toast.makeText(getContext(), "SMS 발송이 되지 않았습니다. 잠시 뒤에 다시 시도 하세요.", Toast.LENGTH_LONG).show();
return;
}
}
}
}

@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}

@Override
public void onBackPressed() {
getParentFragmentManager().beginTransaction()
.replace(R.id.container, MsgSendListFragment.newInstance())
.commitNow();
}
}

 


그럼 오늘도 즐~ 코딩...





반응형






























오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기










앱을 만들었으니 이번에는 플레이 스토어에 등록을 해 보자  등록을 하려면 먼저 개발자 계정으로 사용자 등록을 진행해야 한다.


https://play.google.com/console/signup



 


Google Play Developer Console


하나의 계정으로 모든 Google 서비스를 Google Play Developer Console로 이동하려면 로그인하세요.


accounts.google.com




앱을 등록하려면 개발자 계정을 먼저 생성해야 한다.  


 




개발자 이름,  이메일 주소 , 연락처 전화번호 등등을 입력하고 약관은 체크를 하고 진행 해야 한다.  요새는 워낙 세상이 흉흉하니 가급적이면 구글계정으로 이메일 계정을 하나 만들어 사용하는 것이 좋지 않을 까 하는 생각을 했다.  개인적으로 사용하지 않는 일하는 계정이 좋을 듯 하고, 전화번호도 가급적이면 개인폰 번호 보다는 안심번호를 얻어서 사용하는 것이 좋을 것 같다.   안심번호를 얻는 방법은 크몽 사이트에서 개발자로 등록하면 안심번호가 부여 된다. 


https://kmong.com/



 


프리랜서마켓 No.1 크몽 | 디자인, IT·프로그래밍, 마케팅,번역·통역, 경영진단, 법률 및 취업 관


블로그·카페, 로고·브랜딩, 웹사이트 개발 등 25만명+의 전문가 중에서 나만을 위한 전문가를 리뷰, 포트폴리오, 실시간 견적과 함께 크몽에서 만나보세요


kmong.com




 


계정 생성 및 결제를 클릭해서 진행하면 $25을 결제해야 개발자 계정을 등록할 수 있다.




앱 만들기 버튼을 클릭해서 다음으로 넘어간다.





앱 만들기를 클릭해서 기본사항을 입력하고 나면, 다음은 앱 설정 시작에서 앱 등록 자료를 입력 해야 한다.


 



 


테스트 시작을 통해서 내부테스트/ 공개테스트 / 비공개 테스트 등을 선택할 수 있다. 바로 앱 등록을 할 수 도 있지만, 


테스트를 먼저 진행하게 되면 출시점 점검사항을 미리 확인해 볼 수 있다.




우리는 그냥 등록을 시작해 본다.




해야할 일들을 순서대로 보여주기 떄문에 하나씩 클릭해서 진행하면 된다.


https://play.google.com/store/apps/details?id=com.billcoreatech.ontheway801 



 


가는길에 (On The Way) - Google Play 앱


선택한 위치에 도착하면 사전에 등록한 알림을 보내 줍니다.


play.google.com




 


등록하고 기다리면 위 링크와 같이 playstore 에 등록된 나의 앱을 볼 수 있게 된다.'


 


끝.





반응형
























오늘의 이야기





오늘은 내가 만든 앱에 광고를 달아보자... admob 으로 다가... 그래서 먼저 할 꺼는 admob 에 로그인하고 앱 만들기를 클릭하기




그럼 다음 그림과 같이 나옴.  




나의 앱은 안드로이드 버전이고, 아직 스토어에 등록이 되지 않았다고 선택 - 그리고 계속 하기... 그전에 앱을 스토어에 등록하고 나서 승인도 받아야 정상적으로 광고가 게시될 것 같으니,  그전까지는 테스트 광고만 게시하는 것으로 해야 할 것 같음.  계속을 클릭 하면 다음과 같이 나옴.




앱 이름을 입력하고 - 앱 추가 버튼 클릭 




등록이 되었다네요... 그럼 이제 뭘 해야 하지...  일단 광고 단위를 추가해야 한다. 그래야 앱이 광고를 실행할 수 있으니까.





종류가 많기도 해라... 아무튼 오늘은 처음이니 배너 광고를 선택하고 진행을 해 볼 예정이다.




광고단위 만들기 버튼을 클릭하면 다음과 같이 App ID 와 banner ID 가 나타난다.




위에서 보여주는 ca... 로 되어 있는 것들을 일단, 내 프로젝트의 strings.xml 에 추가 하자.  위에 있는 것은 (내용중에 ~ 표시가 있다) AppId 에 넣고, 아래 꺼(내용중에 / 표시가 있음)는 bannerId 에 넣어준다. 그리고 테스트를 위해서 testId 을 추가한다.


    <string name="AppId" translatable="false">ca-app-p...............589257</string>
<string name="bannerId" translatable="false">ca-app................96255890</string>
<string name="testId" translatable="false">ca-app-pub-3940256099942544/6300978111</string>

 


그 다음은 SDK 가이드를 보라고 하는데,  들여다 보면 다음과 같다. 앱 수준 gradle 파일에 다음을 추가해야 한다.


dependencies {

.....

implementation 'com.google.android.gms:play-services-ads:20.3.0'

}

광고게시를 위해서 필요한 라이브러리를 받을 수 있도록 추가 하여 화면 상단에 sync 을 클릭해서 필요한 라이브러리를 받아온다.


이번에는 manifests.xml 에 다음과 같이 추가 한다.  meta-data 을 추가 한다.  value 에 들어가는 AppId 는 위에서 말한 strings.xml 에 저장한 name 과 같아야 한다.


    <application
android:allowBackup="true"
android:icon="@mipmap/ic_locationnote"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_locationnote_round"
android:supportsRtl="true"
android:theme="@style/Theme.On801"
android:usesCleartextTraffic="true">

<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="@string/AppId"/>

.....

</application>

 이번에는 activity_main.xml 화면 layout 에 뷰를 달아 주어야 한다.


    <com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_weight="1"
app:adSize="BANNER"
app:adUnitId="@string/testId"></com.google.android.gms.ads.AdView>

아직은 우리의 앱이 스토어에 등록이 되지 않았기 때문에 adUnitId 에는 위에서 저장한 testId 을 이용해서 테스트를 진행해야 한다.  구글은 광고가 들어간 앱을 작성자가 클릭하는 것을 싫어하고 제재를 할 수 있기 때문에 테스트 단계에서는 반드시 testId을 사용할 것을 권고하고 있다.   이번에는 MainActivity 에서 아래와 같이 광고를 게재 하면 된다.


    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = MainActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

MobileAds.initialize(this, new OnInitializationCompleteListener() {
@Override
public void onInitializationComplete(InitializationStatus initializationStatus) {
}
});

AdRequest adRequest = new AdRequest.Builder().build();
binding.adView.loadAd(adRequest);
}

 




 이것으로 우리의 앱에 광고를 다는 것도 완성 !!!


광고종류에 따라 다양하게 앱의 화면을 구성해야 하는 것은 아무래도


남은 숙제일 것 같다.


 


 


 


 


 


 


 


 


 


 


이제 다음번에는 스토어에 등록하는 과정을 담아 보도록 하겠다.


 





반응형






























오늘의 이야기


#스하리1000명프로젝트,
Bazen yabancı işçilerle konuşmak zor oluyor, değil mi?
Yardımcı olacak basit bir uygulama yaptım! Siz kendi dilinizde yazarsınız ve başkaları da bunu kendi dillerinde görür.
Ayarlara göre otomatik çeviri yapar.
Kolay sohbetler için son derece kullanışlı. Fırsat bulduğunda bir göz at!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기










3일차는 쫌 그렇게 3번쨰 글 쓰기... 그 사이에 몇날이 흘렀다.   먼저 만들어진 앱을 볼까 ?




 





geofence 는 실행을 등록하면 유효시간을 정하게 되어 있어서


등록할 때 특정 시간을 지정할 수 있도록 선언을 해 주어야 한다.


mGeofenceList.add(new Geofence.Builder()
.setRequestId(mapPOIItem.getItemName() + ":" + keywordBinding.editKeyword.getText().toString())
.setCircularRegion(
mapPoint.getMapPointGeoCoord().latitude,
mapPoint.getMapPointGeoCoord().longitude,
spSetting.getInt("aroundArea", (int) Constants.GEOFENCE_RADIUS_IN_METERS)
)
.setExpirationDuration(StringUtils.getDuration(context))
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.build());

 




geofencelist 에 항목을 추가하면서 parameter 로 유지 시간을 설정할 수 있으므로 샘플 소스와 같이 등록 할 때 유지 시간을 설정한다.


setCircularRegion(위도, 경도, 범위) 를 지정하고


setExpirationDuration(유지시간) 을 입력한다. 유지시간은 ms 단위로 입력을 해야 하기 때문에 12 * 60 * 60 * 1000 으로 입력을 해야 12시간이 유지 된다.


이번에는 지도 보이기, 여기서는 지도를 kakao api 을 이용해서 지도를 표현하고 있으니,   지도 연동은 아래글을 참고 해서 보시길...


https://billcorea.tistory.com/23


 


주변 검색을 하는 방법은 


https://billcorea.tistory.com/48


 


이글을 참고해서 보시면 될 것 같다.


 



 


android geofences 을 활용한 앱 만들기 2일차.


앞에 이야기를 보고 준비를 잘 했다면 이제 하나씩 만들어 보자. 카카오 지도 준비는 되었으니, 이제 카카오 개발자 페이지에서 주변 정보를 수집할 방법에 대하여 생각해 보자, 구글에서도 place


billcorea.tistory.com





 


Kakao 지도 연동...


몇해전에는 카카오 지도를 연동하는 데, 애로 사항을 많이 느꼈다. 카카오의 기술지원은 어디에 있는 것인지 찾을 수 도 없고... 이번에 다시금 도전~ apis.map.kakao.com/android/ 이 페이지는 예나 지금


billcorea.tistory.com




 


다음에 해야할 일은 play store 에 등록하는 것이 될 것 같다.  그 전에 google admob 을 이용해서 앱에 광고를 달아보는 절차를 진행해 볼 생각이다.


 


동영상은 logcat 에서 만들어낸 동영상파일을 하나로 묶어서 너튜브에 게시함,  그러는 이유는 나중에 앱을 등록하는 과정에서 앱의 사용설명서로 링크 하기 위해서... 그리고 이 페이지로 이동하는 링크를 달아 두기 위해서.


 


 





반응형
























오늘의 이야기

오늘은 barcode 인식을 이용한 앱 구현에 대한 정리를 해 볼까 한다.    이 기능 구현의 시작은 어느 티비에서 방송했던, 어쩌다 사장  이라는 방송에서 출연진들이  원래 가게 주인이 적어든 가격표를 찾아가면서 판매를 하는 것을 보고, 단순한 바...