728x90

HTML 을 PDF로 출력하기 위해 테스트하고 간략하게 적어둔다.


###### TCPDF 설치 ############
# 1. 웹 브라우저에서 https://sourceforge.net/projects/tcpdf/files/ 에 접속하여 TCPDF를 다운받는다.
# 현재 기준 tcpdf_6_3_2.zip 가 가장 최신이다.
# 다운로드 받은 파일을 리눅스 /usr/share/php 경로에 업로드 한다.

# 다른 경로에 설치해도 되는데 나눔폰트 설치 경로를 변경하기 싫어서 이곳 저곳 설치해보고 그냥 이 폴더로 정했다.
cd /usr/share/php
unzip tcpdf_6_3_2.zip

# 나눔폰트 설치 방법
cd /usr/share/fonts/
wget http://cdn.naver.com/naver/NanumFont/fontfiles/NanumFont_TTF_ALL.zip

unzip NanumFont_TTF_ALL.zip -d NanumFont
cd /usr/share/php/tcpdf/tools/
ls /usr/share/fonts/NanumFont/*
ls /usr/share/fonts/NanumFont/* | xargs -I TTF php tcpdf_addfont.php -i TTF
ll /usr/share/php/tcpdf/fonts/ | grep nanum

# 예제파일을 참조하여 실행할 php 디렉토리에서 내용을 구현한다.

먼저 화면상으로 원하는 형태로 출력이 되는지 확인하고 최종적으로 PDF 파일로 생성되도록 하면 된다.


tcpdf_include.php 파일에서 경로를 수정해준다.

$tcpdf_include_dirs = array(
    realpath('/usr/share/php/tcpdf/tcpdf.php'),
    '/usr/share/php/tcpdf/tcpdf.php'
);

<?php
// Include the main TCPDF library (search for installation path).
require_once('tcpdf_include.php');

// Set some content to print
$html = <<<EOD
<h3>반갑습니다. PDF센터에 오신 걸 환영합니다.</h3>
<i>This is the first example of TCPDF library.</i>
EOD;

// create new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

// set document information
$pdf->SetCreator(PDF_CREATOR);

// set margins
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);

// Set font
//$pdf->SetFont('nanumgothic', '', 14, '', true);
$pdf->SetFont('nanumgothic');
$pdf->SetDefaultMonospacedFont("nanumgothic_coding");

// Add a page
$pdf->AddPage();

// Print text using writeHTMLCell()
$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);

// Close and output PDF document
//$pdf->Output(getcwd().'/001.pdf', 'F'); // 실제 파일 생성됨
$pdf->Output('001.pdf', 'I'); // 화면으로 보여줌
?>
 



참조 사이트

https://wickedmagic.tistory.com/571

https://zetawiki.com/wiki/TCPDF_%ED%95%9C%EA%B8%80_%ED%8F%B0%ED%8A%B8_%EC%82%AC%EC%9A%A9

'안드로이드 > Android QRCode' 카테고리의 다른 글

Android QRCode 샘플  (0) 2020.08.25
PHP QR code 생성하는 방법  (0) 2020.03.04
QRCode 개요  (0) 2020.03.03
블로그 이미지

Link2Me

,
728x90

특정 문자열을 파싱처리하는 걸 처리하는데 동일한 값이 두번 나온다고 한다.

그래서 두번째 값을 읽어낸다고 한다.

한번만 해당 정보가 출력되는 줄 알았는데 동일한 정보가 여러개 있을 수 있는 환경이다.


String gw_alloc;

private void parseDeviceIPAddress(String msg){
    String[] line = msg.split("\\n"); // 라인 단위로 메시지를 분리하라.
    for(int i=0; i < line.length;i++){
        if(line[i].contains("GW=")){  // GW address 추출
            String str = line[i].trim(); // 문자열 앞의 공백 제거 목적
            int stlocation = str.indexOf("GW=");
            int endlocation = str.length();
            String temp = str.substring(stlocation+3, endlocation-1).trim();
            int firstcomma = temp.indexOf(","); // 처음 나오는 , 를 찾아라.
            String gwaddress = temp.substring(0,firstcomma).trim();
            if(gwaddress.length() > 8){
                gw_alloc = gwaddress;
            }
        }
    }
}


그래서 첫번째 값을 읽어내도록 하는 걸 처리하기 위해서 코드를 아래와 같이 ArrayList를 이용해서 수정했다.

ArrayList<String> gw_alloc = new ArrayList<>();

private void parseDeviceIPAddress(String msg){
    String[] line = msg.split("\\n"); // 라인 단위로 메시지를 분리하라.
    for(int i=0; i < line.length;i++){
        if(line[i].contains("GW=")){  // GW address 추출
            String str = line[i].trim(); // 문자열 앞의 공백 제거 목적
            int stlocation = str.indexOf("GW=");
            int endlocation = str.length();
            String temp = str.substring(stlocation+3, endlocation).trim();
            int firstcomma = temp.indexOf(","); // 처음 나오는 , 를 찾아라.
            String gwaddress = temp.substring(0,firstcomma).trim();
            if(gwaddress.length() > 8){
                gw_alloc.add(gwaddress);
            }
        }
    }
}


해당 정보가 있으면 ArrayList 에 add하고 출력할 때는 gw_alloc.get(0); 를 해주면 첫번째 값을 활용할 수 있다.

블로그 이미지

Link2Me

,
728x90

SharedPreferences 싱글톤 처리 코드이다.

SharedPreferences는 간단한 값을 저장할 때 주로 사용한다.
초기 설정 값이나 자동 로그인 여부 등 간단한 값을 저장할 때 DB를 사용하면 복잡하기 때문에 SharedPreferences를 사용하면 적합하다.
SharedPreferences는 어플리케이션에 파일 형태로 데이터를 저장한다. 데이터는 (key, value) 형태로 data/data/패키지명/shared_prefs 폴더 안에 xml 파일로 저장된다.
해당 파일은 어플리케이션이 삭제되기 전까지 보존된다.
SharedPreferences는 앱의 어디서든 전역적으로 사용하기 때문에 싱글톤 패턴을 사용해서 어디서든 접근 가능하게 만드는 것이 좋다.


import android.content.Context;
import android.content.SharedPreferences;

public class PrefsHelper {
    public static final String PREFERENCE_NAME="pref";
    private Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor prefsEditor;
    private static PrefsHelper instance;

    public static synchronized PrefsHelper init(Context context){
        if(instance == null)
            instance = new PrefsHelper(context);
        return instance;
    }

    private PrefsHelper(Context context) {
        mContext = context;
        prefs = mContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE );
        prefsEditor = prefs.edit();
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        prefsEditor.putInt(key, value).commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }
}


사용법은

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intro);
        mContext = Intro.this;
        PrefsHelper.init(getApplicationContext()); // 한번만 실행하면 된다.
        // 네트워크 연결 검사
        if(Utils.NetworkConnection(mContext) == false) Utils.NotConnected_showAlert(mContext,this);
        checkPermissions();
    }


// 자동 로그인 체크 검사 (읽기)
userID = PrefsHelper.read("userid","");
userPW = PrefsHelper.read("userpw","");


// 쓰기

PrefsHelper.write("userid",userID);

PrefsHelper.write("userpw",userPW);



참고하면 좋은 자료

https://link2me.tistory.com/1528  Java Singleton Design Pattern


https://link2me.tistory.com/1821  Kotlin Singleton 코드 예제


https://link2me.tistory.com/1033  Android Preference

블로그 이미지

Link2Me

,
728x90

MDB 템플릿을 사용하다가 정보 찾는데 걸리는 시간을 절약하기 위해 적어둔다.

bootstrap4 기반으로 만들어진 템플릿이라 bootstrap4 용일 수도 있다.

 

글자 굵은 글씨

<b class="font-weight-bold">'.$R['jiAddress'].' '.$R['aDong'].$bldNM).'</b>

 

글자 왼쪽 정렬

<td class="text-left"><?php echo $R['content'];?></td>

 

빨간 글씨

<td class="text-danger"><?php echo $R['content'];?></td>

 

<?php if($mtype==3):?><td><?php echo $R['d_regis'];?></td><?php endif;?>

 

<tr>
    <th class="text-left" style='width:80pt'>인입구배선</th>
    <td class="text-left" style='width:85%'>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt1" value="1" <?php if(!isset($C['wireEnt'])):?>checked<?php elseif($C['wireEnt'] == "1"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt1">O</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt2" value="2" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "2"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt2">X</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt3" value="3" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "3"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt3">△</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt4" value="4" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "4"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt4">해당없음</label>
        </div>
    </td>
</tr>

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

화면 전환을 한 경우, 로그인 화면은 다시 보이지 않도록 해야 한다.

 

val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
// FLAG_ACTIVITY_CLEARTOP : 실행할 activity가 이미 스택에 존재하면 해당 activity 위에 존재하는 다른 activity 모두 종료시킨다.
// FLAG_ACTIVITY_SINGLE_TOP : 호출하는 activity가 자신을 가리키는 경우, 기존의 activity를 재활용한다.
// 활동의 인스턴스가 이미 현재 작업의 맨 위에 있으면 시스템은 활동의 새 인스턴스를 생성하지 않고
// onNewIntent() 메서드를 호출하여 인텐트를 기존 인스턴스로 라우팅한다.
// https://developer.android.com/guide/components/activities/tasks-and-back-stack 읽어보면 된다.
// FLAG_ACTIVITY_CLEAR_TASK :
// FLAG_ACTIVITY_NEW_TASK : service, notification 등에서 액티비티를 시작하기 위해선 꼭 붙여야 한다.
// 스택에 없으면 새로운 task 생성하고 launch 시키고, 스택에 있으면 해당 task가 Foreground로 온다.
startActivity(intent)
finish() // 현재 activity 제거. login 화면은 다시 사용하지 않도록 하기 위해서
 

 

 

 

 

메모를 추가/수정하고, AddEditMemoActivity 를 종료하면서, 아래와 같은 FLAG를 지정한 경우

val intent = Intent(this@AddEditMemoActivity, MainActivity::class.java)
intent.apply {
    this.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
startActivity(intent)
overridePendingTransition(R.anim.fadein, R.anim.fadeout)
finish()
 

 

MainActivity 는 onNewIntent 를 수행하므로 여기에 필요한 코드를 추가한다.

 
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    Log.d(TAG, "onNewIntent()")
    getMemoFromServer()
}
블로그 이미지

Link2Me

,
728x90

Java 기반 volley 라이브러리에 대한 사용예제는 https://link2me.tistory.com/1533 를 참조하면 된다.


앱 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.link2me.android.volley"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
}


AndroidManifest.xml

- 필요한 퍼미션을 추가한다.

- android:usesCleartextTraffic="true" 를 추가한다. Android 9 이상에서 http 통신 가능하도록 하기 위해서.

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <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/AppTheme.NoActionBar"
        android:usesCleartextTraffic="true">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity" />
    </application>

</manifest>
 


activity_login.xml

- https://link2me.tistory.com/1807 참조


LoginActivity.kt

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.telephony.PhoneNumberUtils
import android.telephony.TelephonyManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import kotlinx.android.synthetic.main.activity_login.*
import org.json.JSONException
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.HashMap

class LoginActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        context = this@LoginActivity
        initView()
    }

    fun initView() {
        // 코틀린에서는 더 이상 findViewById()를 사용하지 않아도 된다.
        // Kotlin Android Extension 만 적용시키면, 레이아웃 import 시키는 것만으로도 XML에서 정의내린 위젯들을 Id로 읽어들인다.
        btn_login.setOnClickListener{
            val url: String = "http://www.abc.com/mobile/mlogin.php"
            val loginID: String = et_id.text.toString().trim()
            val loginPW: String = et_pw.text.toString().trim()
            loginVolley(this, url, loginID, loginPW)
        }
    }

    private fun loginVolley(context: Context, url: String, userid: String, password: String) {
        // https://developer.android.com/training/volley/simple GET 방법

        // 1. RequestQueue 생성 및 초기화
        val requestQueue = Volley.newRequestQueue(context)

        // 2. Request Obejct인 StringRequest 생성
        val request: StringRequest = object : StringRequest(Method.POST, url,
            Response.Listener { response ->
                showJSONList(response)
            },
            Response.ErrorListener { error ->
                Toast.makeText(context, error.toString(), Toast.LENGTH_LONG).show()
            }
        ) {
            override fun getParams(): Map<String, String> {
                val params: MutableMap<String,String> = HashMap()
                params["userid"] = userid
                params["password"] = password
                params["mobileNO"] = getPhoneNumber() // 로그인하는 휴대폰번호 정보
                params["uID"] = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
                return params
            }
        }
        // 3) 생성한 StringRequest를 RequestQueue에 추가
        requestQueue.add(request)
    }

    fun showJSONList(response: String) {
        try {
            Log.e("TAG",response)
            val jsonObject = JSONObject(response)
            jsonObject.let {
                if(it.getString("userinfo") == null) {
                    showAlert(it.getString("result").toString(), it.getString("message").toString())
                } else {
                    if(it.getString("result").toString().equals("success")){
                        val jsonInfo = JSONObject(it.getString("userinfo").toString())
                        jsonInfo.let{
                            // Preference 에 대한 정보 기록은 생략한다.
                            startActivity(Intent(this,MainActivity::class.java))
                        }
                    }
                }
            }
        } catch (e: JSONException) {
            e.printStackTrace()
        }
    }

    companion object {
        var context: Context? = null

        @SuppressLint("MissingPermission") // TED퍼미션을 미리 설정했다는 가정하에
        fun getPhoneNumber(): String {
            var phoneNumber = ""
            try {
                val telephony = context?.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
                if (telephony.line1Number != null) {
                    phoneNumber = telephony.line1Number
                } else {
                    if (telephony.simSerialNumber != null) {
                        phoneNumber = telephony.simSerialNumber
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            if (phoneNumber.startsWith("+82")) {
                phoneNumber = phoneNumber.replace("+82", "0")
            }
            phoneNumber = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                PhoneNumberUtils.formatNumber(
                    phoneNumber,
                    Locale.getDefault().country
                )
            } else {
                PhoneNumberUtils.formatNumber(phoneNumber)
            }
            return phoneNumber
        }

        fun showAlert(title: String, message: String) {
            val builder = context?.let {
                AlertDialog.Builder(it)
                    .setTitle(title)
                    .setMessage(message)
                    .setCancelable(false)
                    .setPositiveButton("OK") { dialog, id -> dialog.dismiss() }
            }
            val alert = builder!!.create()
            alert.show()
        }
    }

}


블로그 이미지

Link2Me

,
728x90

Java 코드로 된 Tedpermission 을 코틀린에서 사용하기 위한 방법이다.

자바로 된 코드를 코틀린 파일에 붙여넣기 하면 대부분 기초적인 변환이 이루어진다.

이후 약간의 손을 보면 사용이 가능해진다.

 

앱 build.gradle

 

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.link2me.android.tedpermission"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'gun0912.ted:tedpermission:2.0.0'
}

2021.7월 확인사항

implementation 'io.github.ParkSangGwon:tedpermission:2.3.0' 로 TED Permission 이 변경되었더라.

 

 

AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <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/AppTheme">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


LoginActivity.kt

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.TedPermission

class LoginActivity : AppCompatActivity() {
    var context: Context? = null

    var permissionlistener: PermissionListener = object : PermissionListener {
        override fun onPermissionGranted() { // 권한 허가시 실행 할 내용
            initView()
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
            // 권한 거부시 실행  할 내용
            Toast.makeText(this@LoginActivity, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT)
                .show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        context = this@LoginActivity;
        checkPermissions()
    }

    private fun checkPermissions() {
        if (Build.VERSION.SDK_INT >= 26) { // 출처를 알 수 없는 앱 설정 화면 띄우기
            val pm: PackageManager = context!!.getPackageManager()
            Log.e("Package Name", packageName)
            if (!pm.canRequestPackageInstalls()) {
                startActivity(
                    Intent(
                        Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
                        Uri.parse("package:$packageName")
                    )
                )
            }
        }

        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                .setPermissionListener(permissionlistener)
                .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                .setPermissions(
                        Manifest.permission.READ_PHONE_STATE,
                        Manifest.permission.READ_CALL_LOG,  // 안드로이드 9.0 에서는 이것도 추가하라고 되어 있음.
                        Manifest.permission.CALL_PHONE,  // 전화걸기 및 관리
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                ).check()
        } else {
            initView()
        }
    }

    fun initView() {

    }
}

 

'안드로이드 > Kotlin 기능' 카테고리의 다른 글

[코틀린] Activity 전환 Intent  (0) 2020.05.09
[코틀린] volley 라이브러리 사용예제  (0) 2020.05.09
[코틀린] PrefsHelper  (0) 2020.05.04
[코틀린] ViewPager 만들기  (0) 2020.04.24
[코틀린] webView 예제1  (0) 2020.04.20
블로그 이미지

Link2Me

,
728x90

프로퍼티(Property)

Java에서는 데이터르 필드에 저장하며, 멤버 필드의 가시성은 보통 비공개(private)이다.

클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근하는 통로로 쓸 수 있는 접근자 메소드를 제공한다.

보통은 필드를 읽기 위한 getter를 제공하고 필드를 변경 허용해야 할 경우 setter를 추가 제공할 수 있다.

Java에서는 필드와 접근자를 묶어 property 라구 부르며, 프로퍼티를 활용하는 프레임워크가 많다.

코틀린은 프로퍼티를 언어 기본 기능으로 제공한다.

class Person(val name: String, val age: Int)

코틀린에서는 Java 클래스의 getter를 val 프로퍼티처럼 사용할 수 있고, getter/setter 쌍이 있는 경우에는 var 프로퍼티처럼 사용할 수 있다.


코틀린에서는 하나의 파일에 하나 또는 여러개의 클래스를 정의할 수 있다.

하지만 유지보수의 용이성을 고려하여 가급적 하나의 클래스를 하나의 파일에 정의하는 것이 좋다.


가시성 제한자(visibility modifier)

 public

 함수나 속성이 클래스 외부에서 사용될 수 없다.

 코틀린의 기본 가시성은 public 이므로 가시성 제한자를 생략해도 된다.

 private

 함수나 속성이 정의된 클래스 내부에서만 사용될 수 있다.

 protected

 함수나 속성이 정의된 클래스 내부 또는 클래스의 서브 클래스에서만 사용될 수 있다.

 internal

 함수나 속성이 정의된 클래스가 포함된 모듈에서 사용될 수 있다.


코틀린에서는 자바에 없는 internal 가시성을 지원한다. 이것은 같은 모듈에 있는 클래스, 함수, 속성까리 상호 사용할 수 있다는 것을 뜻한다.


Java는 기본적으로 패키지 가시성을 지원한다. 즉, 가시성 제한자가 없는 메서드, 필드, 클래스는 같은 package에 있는 클래스에서만 사용 가능하다는 뜻이다.

코틀린에서는 package 가시성이 없다. 같은 package에 있는 클래스, 함수, 속성 등은 기본적으로 상호 사용할 수 있어서 굳이 별도의 가시성을 가질 필요가 없기 때문이다.


서로 다른 패키지에 있는 같은 이름의 클래스나 함수 등을 함께 사용할 경우에는 as 키워드로 별칭을 지정하여 이름 충돌이 생기지 않게 할 수 있다.


클래스 속성에서 var 는 변경 가능한 속성을, val 은 읽기 전용 속성을 지정하는 데 사용된다.


코틀린 생성자

코틀린은 자바, C++과는 달리 클래스 선언 부분에서 이름에 괄호를 붙여 생성자를 만들 수 있다.

이를 주 생성자(primary constructor)라고 한다.

코틀린 몸체에 보조 생성자가(secondary constructor)를 추가할 수 있다. 보조 생성자는 매개변수를 다르게 하여 여러 개를 정의할 수 있다.

    // (name: String, surname: String)  default constructor signature
    class Person(_name: String, surname: String) {
        // 맨 앞에 밑줄(_)이 있는 변수는 임시 변수를 나타낸다.
        // 매개변수를 포함해서 임시 변수는 한번 이상 참조될 필요가 없는 변수이며,
        // 1회용이라는 것을 나타내기 위해 이름 앞에 밑줄을 붙인다.

        val name: String

        // init block , represents the body of default constructor
        init { //주생성자와 함께 초기화 블럭
            // init 초기화 블록은 여러개 가능하다.
            name = _name
            Log.d("primary", name)
        }

        // secondary constructor
        // this(name,"") call to default constructor
        // constructor 로부터 생성된 생성자는 '기본 생성자'를 '상속' 받아야 한다.
        constructor(name : String):this(name,""){
            Log.d("secondary", "Hello")
        }
    }


class Person constructor(name: String) { /*...*/ }

primary constructor가 어노테이션이나 접근 제한자(public, private 등)을 갖고 있지 않다면 constructor 키워드를 생략할 수 있다.
class Person(name: String) { /*...*/ }

자바의 경우 생성자로부터 받아온 변수를 해당 클래스에서 사용할 때 생성자 내에서 [this.클래스변수 = 매개변수] 같은 형식으로 받아올 수 있다. 하지만 코틀린은 생성자 영역이 없어서 이렇게 할 수가 없다. 

코틀린에서는 기본과 보조 생성자에 추가하여 클래스 초기화 블럭도 정의할 수 있다.

초기화 블럭은 init 키워드로 정의한다. init 초기화 블록은 여러개 가능하다.

this.name = name

만일 속성을 초기화하는 코드가 복잡하다면 초기화 코드를 함수나 초기화 블럭에 넣은 것을 고려하자.


코틀린의 클래스는 secondary constructor도 가질 수 있다.
이때 사용되는 키워드는 constructor이고, primary constructor와 다르게 생략할 수 없다.
class Person {
    var children: MutableList<Person> = mutableListOf<Person>();
    constructor(parent: Person) {
        parent.children.add(this)
    }
}



블로그 이미지

Link2Me

,
728x90

null 은 var 나 val 변수의 값이 없다는 것을 나타내는 특별한 값이다. 자바를 포함해서 많은 프로그래밍 언어에서 null 은 흔히 크래시(crash)를 유발하는 원인이 된다.


코틀린은 컴파일 언어다. 즉 실행에 앞서 컴파일러라는 특별한 프로그램에 의해 기계어로 변환된다는 걸 뜻한다.

컴파일러는 null 가능 타입에 null 값이 지정되는지 검사한다.


런타임 에러는 컴파일러가 발견할 수 없어서 프로그램이 컴파일된 후 실행 중에 발생하는 오류다.

자바는 null 가능 타입과 불가능 타입을 구분하지 않으므로 자바 컴파일러는 null 값으로 인한 문제가 있는지 사전에 알려줄 수가 없다. 따라서 정상적으로 컴파일 된 코드가 런타임 시에 크래시될 수 있다.


null 가능 타입이 컴파일이나 런타임 에러 없이 항상 실행되도록 해야 할 경우가 있다.

이런 경우에 null 가능 타입을 안전하게 처리하기 위해 안전 호출 연산자(safe call operator)인 ?.을 사용한다.

non-null 단언 연산자(assertion operator)인 !!도 null 가능 타입에 대해 함수를 호출하기 위해 사용할 수 있다.

non-null 단언 연산자는 컴파일러가 null 발생을 미리 알 수 없는 상황이 생길 수 있을 때 사용된다.

nul 가능 변수에 !! 연산자를 사용하는 것은 위험하다.


예기치 않는 값으로 인해 우리가 의도치 않는 방향으로 프로그램이 실행될 수 있다. 따라서 코드 값을 작성할 때는 입력 값을 검사하는데 많은 노력을 기울여야 한다.


null 불가능 매개변수가 null 인자를 받지 않게 해주는 매커니즘에는 @NotNull 애노테이션이 있다.

@NotNull 애노테이션은 많은 자바 프로젝트 코드에서만 볼 수 있지만, 코틀린에서 자바 메서드를 호출할 때 특히 유용하다.


출처 : 빅너드랜치의 코틀린 프로그래밍에서 발췌 요약

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9791188621538&orderClick=LAG&Kc=







블로그 이미지

Link2Me

,
728x90

SharedPreferences는 간단한 값을 저장할 때 주로 사용한다.
초기 설정 값이나 자동 로그인 여부 등 간단한 값을 저장할 때 DB를 사용하면 복잡하기 때문에 SharedPreferences를 사용하면 적합하다.
SharedPreferences는 어플리케이션에 파일 형태로 데이터를 저장한다. 데이터는 (key, value) 형태로 data/data/패키지명/shared_prefs 폴더 안에 xml 파일로 저장된다.
해당 파일은 어플리케이션이 삭제되기 전까지 보존된다.
SharedPreferences는 앱의 어디서든 전역적으로 사용하기 때문에 싱글톤 패턴을 사용해서 어디서든 접근 가능하게 만드는 것이 좋다.


아래 코드는 https://link2me.tistory.com/1819 에서 Java로 구현했던 코드를 약간 수정해서 자바 코드를 Kotlin 으로 자동 변환한 다음에 다시 companion object 로 옮기면서 테스트하고 적어둔다.


예제1)

import android.content.Context
import android.content.SharedPreferences

class PrefsHelper {
    companion object{
        // 물음표(?)는 해당 타입의 null 가능 버전을 나타낸다.
        private var prefs: SharedPreferences? = null
        private var editor: SharedPreferences.Editor? = null
        private var prefmodule: PrefsHelper? = null
        val PREF_NAME = "pref"

        val BaudRate = "BaudRate"
        val DataBit = "DataBit"
        val StopBit = "StopBit"
        val Parity = "Parity"
        val Settings = "0"

        fun getInstance(context: Context): PrefsHelper? {
            if (prefmodule == null) {
                prefmodule = PrefsHelper()
            }
            if (prefs == null) {
                prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
                editor = prefs!!.edit()
            }
            return prefmodule
        }
    }

    fun getString(key: String, defValue: String): String {
        // ?. 안전호출 연산자(safe call operator) → NullPointerException 방지
        // !!  non-null 단언 연산자
        return prefs?.getString(key, defValue)!!
    }

    fun putString(key: String, value: String) {
        // !!  non-null 단언 연산자
        prefs!!.edit().apply{
            putString(key, value)
        }.apply()
    }

    fun getBoolean(key: String, defValue: Boolean): Boolean {
        return prefs?.getBoolean(key, defValue)!!
    }

    fun putBoolean(key: String, value: Boolean) {
        prefs!!.edit().apply{
            putBoolean(key, value)
        }.apply()
    }

    fun getInt(key: String, defValue: Int): Int {
        return prefs!!.getInt(key, defValue)
    }

    fun putInt(key: String, value: Int) {
        prefs!!.edit().apply{
            putInt(key, value)
        }.apply()
    }
}


MainActivity.kt 에서 쓰기와 읽기 테스트 한 예제

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        writePreferences("115200", "8", "1", 0)

        val BaudRate: String =  PrefsHelper.getInstance(this)!!.getString(PrefsHelper.BaudRate, "")
        val DataBit: String =  PrefsHelper.getInstance(this)!!.getString(PrefsHelper.DataBit, "")
        val StopBit: String =  PrefsHelper.getInstance(this)!!.getString(PrefsHelper.StopBit, "")
        val Parity: String = PrefsHelper.getInstance(this)!!.getString(PrefsHelper.Parity, "")

        Log.e("Baurate",BaudRate)
        Log.e("DataBit",DataBit)
        Log.e("StopBit",StopBit)
        Log.e("Parity",Parity)
    }

    private fun writePreferences(baudRate: String, dataBit: String, stopBit: String, parity: Int) {
        // !!.  non-null 단언 연산자
        PrefsHelper.getInstance(this)!!.putString(PrefsHelper.BaudRate, baudRate)
        PrefsHelper.getInstance(this)!!.putString(PrefsHelper.DataBit, dataBit)
        PrefsHelper.getInstance(this)!!.putString(PrefsHelper.StopBit, stopBit)
        PrefsHelper.getInstance(this)!!.putString(PrefsHelper.Parity, parity.toString())
        PrefsHelper.getInstance(this)!!.putString(PrefsHelper.Settings, "0")
    }
}


예제2)

import android.content.Context
import android.content.SharedPreferences

class PrefsHelper(context: Context) {

    companion object {
        const val PREFERENCE_NAME = "pref"
        private lateinit var prefs: SharedPreferences
        private lateinit var prefsEditor: SharedPreferences.Editor
        private var instance: PrefsHelper? = null

        fun init(context: Context): PrefsHelper? {
            if (instance == null) instance = PrefsHelper(context)
            return instance
        }

        fun read(key: String?, defValue: String?): String {
            return prefs.getString(key, defValue)!!
        }

        fun write(key: String?, value: String?) {
            prefsEditor.putString(key, value)
            prefsEditor.commit()
        }

        fun read(key: String?, defValue: Int): Int {
            return prefs.getInt(key, defValue)
        }

        fun write(key: String?, value: Int?) {
            prefsEditor.putInt(key, value!!).commit()
        }

        fun read(key: String?, defValue: Boolean): Boolean {
            return prefs.getBoolean(key, defValue)
        }

        fun write(key: String?, value: Boolean) {
            prefsEditor.putBoolean(key, value)
            prefsEditor.commit()
        }
    }

    init {
        prefs = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
        prefsEditor = prefs.edit()
    }

}

사용법

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_intro)
        mContext = this@Intro
        PrefsHelper.init(applicationContext) // 한번만 실행하면 된다.
    }


PrefsHelper.write("userid", userID)
PrefsHelper.write("userNM", Value.decrypt(result.userinfo!!.userNM))


블로그 이미지

Link2Me

,
728x90

안드로이드에서 Java Singleton 패턴으로 SharedPreferences 처리를 하는 코드를 적어둔다.


import android.content.Context;
import android.content.SharedPreferences;

public class PrefsHelper {
    private static Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor editor;
    private static PrefsHelper prefmodule = null;
    public static final String PREF_NAME ="pref";

    public static final String BaudRate = "BaudRate";
    public static final String DataBit = "DataBit";
    public static final String StopBit = "StopBit";
    public static final String Parity ="Parity";

    public static PrefsHelper getInstance(Context context) {
        mContext = context;

        if (prefmodule == null) {
            prefmodule = new PrefsHelper();
        }
        if(prefs==null){
            prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
            editor = prefs.edit();
        }
        return prefmodule;
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        SharedPreferences.Editor prefsEditor = prefs.edit();
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        SharedPreferences.Editor prefsEditor = prefs.edit();
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        SharedPreferences.Editor prefsEditor = prefs.edit();
        prefsEditor.putInt(key, value).commit();
    }
}


사용법 write

private void writePreferences(String baudRate, String dataBit, String stopBit, int parity){
    PrefsHelper.getInstance(this).write(PrefsHelper.BaudRate, baudRate);
    PrefsHelper.getInstance(this).write(PrefsHelper.DataBit, dataBit);
    PrefsHelper.getInstance(this).write(PrefsHelper.StopBit, stopBit);
    PrefsHelper.getInstance(this).write(PrefsHelper.Parity, String.valueOf(parity));
}


읽기

String BaudRate = PrefsHelper.getInstance(mContext).read(PrefsHelper.BaudRate,null);
String DataBit = PrefsHelper.getInstance(mContext).read(PrefsHelper.DataBit,null);
String StopBit = PrefsHelper.getInstance(mContext).read(PrefsHelper.StopBit,null);
String Parity = PrefsHelper.getInstance(mContext).read(PrefsHelper.Parity,null);




다른 예제

이 예제를 활용하면 여러모로 편하다.

import android.content.Context;
import android.content.SharedPreferences;

public class PrefsHelper {
    public static final String PREFERENCE_NAME="pref";
    private Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor prefsEditor;
    private static PrefsHelper instance;

    public static synchronized PrefsHelper init(Context context){
        if(instance == null)
            instance = new PrefsHelper(context);
        return instance;
    }

    private PrefsHelper(Context context) {
        mContext = context;
        prefs = mContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE );
        prefsEditor = prefs.edit();
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        prefsEditor.putInt(key, value).commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }
}


사용방법

PrefsHelper.init(getApplicationContext()); // 한번만 실행하면 된다.


// 쓰기

PrefsHelper.write("userid",userID);
PrefsHelper.write("userpw",userPW);


// 읽기

userID = PrefsHelper.read("userid","");
userPW = PrefsHelper.read("userpw","");

블로그 이미지

Link2Me

,