두 수를 입력받아 합을 출력하는 간단한 프로그램이다.

 

# 전체 구조

 

0. layout

- EditText 두 개, Button 하나, TextView 하나로 구성

 

 

1. MyApplication

package com.monkey.mvpstudy

import android.app.Application
import android.content.Context
import android.content.SharedPreferences

class MyApplication : Application() {
    companion object {
        lateinit var context: Context
        lateinit var pref: SharedPreferences
    }

    init {
        context = this
    }
}

- SharedPrefence를 사용하기 위해서 사용

- Manifest의 <application> 태그 내에 android:name=".Myapplication"으로 사용

 

 

2. Contract

package com.monkey.mvpstudy

interface Contract {
    interface View {
        fun setValue(num1: Int, num2: Int, num3: Int)
        fun showResult(num: Int)
    }

    interface Presenter {
        fun getSum(num1: Int, num2: Int)
        fun save(num1: Int, num2: Int)
        fun load()
    }
}

- 뷰단에서는 값을 설정하고 결과를 보여주도록 할 것이다.

- 프레젠터단에서는 합을 구하고 저장하고 불러오도록 할 것이다.

 

 

3. MainModel

package com.monkey.mvpstudy

class MainModel {
    val presenter: Contract.Presenter

    constructor(presenter: Contract.Presenter) {
        this.presenter = presenter
    }

    fun getSum(num1: Int, num2: Int): Int {
        return num1+num2
    }
}

- 모델에서는 프레젠터를 참고하고 있음

- getSum이라는 함수로 합계를 구하도록 하였음

 

 

4. MainActivity

package com.monkey.mvpstudy

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.monkey.mvpstudy.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), Contract.View {
    val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    val presenter by lazy {
        MyPresenter(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        MyApplication.pref = getSharedPreferences("myApp", MODE_PRIVATE)

        presenter.load()    //SharedPreference에 저장한 값을 세팅

        binding.btnOk.setOnClickListener {
            val num1 = binding.etNum1.text.toString().toInt()
            val num2 = binding.etNum2.text.toString().toInt()
            presenter.getSum(num1, num2)
            presenter.save(num1, num2)
        }
    }

    override fun setValue(num1: Int, num2: Int, num3: Int) {
        binding.etNum1.setText(num1.toString())
        binding.etNum2.setText(num2.toString())
        showResult(num3)
    }

    override fun showResult(num: Int) {
        binding.tvResult.text = num.toString()
    }
}

- 맨 처음 onCreate 호출 시 저장한 값을 불러오도록 함

- 버튼을 누르면 presenter에게 합을 구하고 값을 저장하도록 함

- Contract의 View를 상속받았으므로 setValue와 showResult 구현

 

 

5. MyPresenter

package com.monkey.mvpstudy

import androidx.core.content.edit

class MyPresenter : Contract.Presenter {
    companion object {
        val _P_NUMBER1 = "number1"
        val _P_NUMBER2 = "number2"
    }
    var view : Contract.View
    var model : MainModel

    constructor(view: Contract.View) {
        this.view = view
        this.model = MainModel(this)
    }

    override fun getSum(num1: Int, num2: Int) {
        val sum = model.getSum(num1, num2)
        view.showResult(sum)
    }

    override fun save(num1: Int, num2: Int) {
        MyApplication.pref.edit {
            putInt(_P_NUMBER1, num1)
            putInt(_P_NUMBER2, num2)
        }
    }

    override fun load() {
        val num1 = MyApplication.pref.getInt(_P_NUMBER1, 0)
        val num2 = MyApplication.pref.getInt(_P_NUMBER2, 0)
        val sum = model.getSum(num1, num2)
        view.setValue(num1, num2, sum)
    }
}

- Contract의 Presenter을 상속받아 getSum, save, load를 구현

- getSum은 코드의 중복과 확장성을 위해 model의 getSum으로 로직을 옮김

 

 

 

 

 

 

 

링크는 프로덕션 버전이 출시 완료되면 올리겠습니다.

일단은 스크린샷부터 :D

내일쯤이면 검토가 끝나지 않을까 싶은데~

두근두근하네요^^

 

'개발 > Android' 카테고리의 다른 글

[Kotlin/코드] MVP (2)  (0) 2022.02.14
[Kotlin/코드] MVP 패턴  (1) 2022.02.14
[프로젝트] 8일차 - 캡처 & FileProvider & 공유  (0) 2021.09.27
[프로젝트] 7일차 - Room ( Database )  (0) 2021.09.25
[프로젝트] 6일차 - atan2  (0) 2021.09.25

1. 화면 캡처

원래는 view.getDrawingCache()를 사용했으나 이게 deprecated 되어서 다른 방법으로 캡처 기능을 구현하였다.

 

https://newbedev.com/view-getdrawingcache-is-deprecated-in-android-api-28

 

Programming tutorials | Newbedev

Checkout new tutorials and guides for programming languages Javascript, Python, Java, Golang, C++, PHP

newbedev.com

아래는 뷰를 캡쳐해서 저장된 파일의 경로를 가져오는 capture 메소드

fun capture(): String? {
    val now = SimpleDateFormat("yyyyMMdd_hhmmss").format(Date(System.currentTimeMillis()))
    val mPath = cacheDir.absolutePath+"/$now.jpg"

    var bitmap: Bitmap? = null
    val captureView = window.decorView.rootView	//캡처할 뷰

    bitmap = Bitmap.createBitmap(captureView.width, captureView.height, Bitmap.Config.ARGB_8888)

    var canvas = Canvas(bitmap)	
    captureView.draw(canvas)	

    if(bitmap == null) {
        return null
    }else {
        val imageFile = File(mPath)
        val outputStream = FileOutputStream(imageFile)
        outputStream.use {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
            outputStream.flush()
        }
    }
    return mPath
}

 

2. FileProvider

https://keykat7.blogspot.com/2021/02/android-fileprovider.html

나는 캐시 디렉토리 안에 screenshots라는 폴더 안에 스크린샷을 저장하려고 했다.

그런데 파일에서 Uri를 얻기 위해서 FileProvider를 이용하여야 한다.

 

① resource에 fileprovider.xml 추가

<paths>
    <cache-path
        name="screenshots"
        path="." />
</paths>

② Manifest.xml에 코드 추가 

<application
    ...
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.test.packagename.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/fileprovider" />
    </provider>
    ...
/>

③ 이미지 공유하는 코드 추가

if(captureFile == null) {
    //캡쳐 실패한 경우
}else{
    try {
        val intent = Intent(Intent.ACTION_SEND)
        val fileUri : Uri? = FileProvider.getUriForFile(getContext(), "$packageName.fileprovider", captureFile!!)
        fileUri?.let {
            intent.putExtra(Intent.EXTRA_STREAM, fileUri)
            intent.type = "image/*"
            intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
            startActivity(intent)
        }
    }catch(e: ActivityNotFoundException) {
        //공유할 수 있는 어플리케이션이 없을 때 처리
    }
}

 

 

리스트를 내부 DB에 저장하고 불러오게 하고 싶었다.

라떼는 SQLite에 직접 접근해서.. (이하 생략)

요즘은 세상이 좋아져서 Room이라는 걸로 대신해서 쓸 수 있는 모양이다.

 

Room을 이해하기 위해서는 RoomDatabase, Dao, Entity 이렇게 세 가지를 이해해야하는데

이것은 다른 사람의 블로그나 공식 문서를 보면 아주 잘 설명이 되어 있으니

굳이 내가 적지는 않겠다.

 

다만, 나의 경우 데이터 모델이

val idx: Int
var title: String
var items: Array<String>

처럼 Array를 포함하고 있었는데, 이 경우에는 DB에 Array를 JSON 타입으로 저장하여야 했다.

그리고 이를 위해 @TypeConverter, @TypeConverters 어노테이션을 이용하여야 했다.

 

이는 아래 블로그를 참고하기 바란다.

https://jinsangjin.tistory.com/56

 

Room에서 List 사용하기 #Kotlin #TypeConverter #Android

Room에서 다음과 같이 선언하면 그냥 쓸 수 있을 줄 알았다. @ColumnInfo(name = "word_list") var wordList: List ?= null 하지만 다음과 같이 선언하고 build를 시작하면 error가 뜬다. 에러내용은 "typerConver..

jinsangjin.tistory.com

 

마지막으로 Room을 통한 DB 접속은 메인 스레드에서 실행될 수 없다.

따라서 코루틴을 사용하여 접근하였는데

 

CoroutineScope(Dispatchers.Main).launch {
    var dataList: ArrayList<SaveDTO>? = null
    CoroutineScope(Dispatchers.Default).async {
        val db = AppDatabase.getInstance(context)
        //DB에서 하는 일들
    }.await()
	//메인 스레드에서 하는 일들
}

이런 식으로 사용하였다.

 


이제 기본 기능은 얼추 완성된 것 같다.

남은 건 스샷찍어서 공유하는 것 뿐이려나..

Math.atan2를 사용하면 두 점 사이의 라디안 각도를 얻을 수 있다.

 

상대적인 위치이기 때문에 

 

Math.atan2(나중점.x - 먼젓번점.x, 나중점.y - 먼젓번점.y)를 해서 사용한 뒤

 

나온 값에 180을 곱하고 Math.PI로 나누어주면 우리가 아는 각도가 나온다.

 

나는 이걸 이용해서 설정모드일 때는 값을 설정하도록,

설정모드가 아닐 때는 터치한 곳의 값을 표시하도록 하였다.

 

+ Recent posts