android - Hilt 사용기

모바일/안드로이드 2021. 1. 15. 14:29 Posted by 아는 개발자

예전에 쓴 Hilt 포스트에선 기존에 사용중인 프로젝트에 Hilt를 쉽게 적용할 수 없어 아쉽다는 점을 다루었다. 그래서 최근에 소소하게 시작한 사이드프로젝트에선 처음부터 Hilt를 도입해서 사용해봤다. 확실히 Dagger에 비해 자유롭고 사용하기가 간편했다. 이번 포스트에서는 어떤점이 좋았는지를 다뤄보고자 한다. 

 

1. private val 변수 형태로 주입 가능.

 

Dagger로 의존성을 주입할 때는 @Inject 어노테이션과 뒤에 lateinit var 을 붙여줘야했다. 그런데 앞으로 바뀌지 않을 변수에 var 형태로 선언하는게 여간 찝찝한게 아니었다. 다행히 Hilt에서는 이런 찝찝함을 해결했다. 생성자의 인자로 추가해 의존성을 주입할 수 있어 값이 변경되지 않은 val 형태로 주입이 가능하다. 아래 코드는 @ViewModelInject 어노테이션을 이용해 module에서 선언된 객체들에 바로 의존성을 주입하는 코드다. private 변수로도 주입이 가능하다.

 

class AssetEditorViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    application: Application,
    private val assetRepository: AssetRepository,
    private val assetTypeRepository: AssetTypeRepository
): AndroidViewModel(application) {

}

@Module
@InstallIn(ApplicationComponent::class)
class DatabaseModule {
    ...

    @Singleton
    @Provides
    fun provideAssetRepository(appDatabase: AppDatabase) = AssetRepository(appDatabase.assetDao())

    @Singleton
    @Provides
    fun provideAssetTypeRepository(appDatabase: AppDatabase) = AssetTypeRepository(appDatabase.assetTypeDao())
}

 

물론 activity, fragment 처럼 생성자를 customize 할 수 없는 클래스도 있다. 이런 경우 기존과 동일하게 lateinit var를 붙인 채로 주입이 가능하다.

 

@AndroidEntryPoint
class MainFragment : BaseFragment(R.layout.fragment_main) {

    @Inject lateinit var assetRepository: AssetRepository

 

2. ViewModel 의존성 주입이 쉽다

 

Dagger에서는 ViewModel 을 공식적으로 지원해주는게 아니어서 별도의 Factory 클래스를 만들어서 주입을 해줘야 했다. 예로 Fragment를 만들면 이 Fragment Module에선 주입할 ViewModel을 팩토리 형태로 만들어줘야하고 ViewModelMap에 따로 등록도 해줘야하고 결과적으로 코드가 너무 늘어나 관리가 어렵다. Hilt에서는 ViewModel 의존성 주입을 공식적으로 지원해주기 시작했다.

 

ViewModel은 @ViewModelInject 어노테이션을 생성자 앞에 붙이고 ViewModel에서 사용하려는 의존성 주입 클래스를 선언만 하면 된다. Activity, Fragment 단에서는 코틀린 delegate 속성인 by viewModels(), by activityViewModels()를 통해 ViewModel을 받으면 평소와 동일하게 사용할 수 있다.

 

@AndroidEntryPoint
class MainActivity : BaseActivity() {
    private val mainViewModel: MainViewModel by viewModels()
}

@AndroidEntryPoint
class AssetsFragment: Fragment(R.layout.fragment_assets) {
    private val mainViewModel: MainViewModel by activityViewModels()
}

class MainViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    application: Application,
    private val accountRepository: AccountRepository,

 

3. Module 만들고 등록 할 필요가 없다.

 

Dagger에서는 어떤 Module을 만들면 Dagger에 등록해주는 Module에다가 추가해야했다. 그래서 열심히 Module을 만들어도 추가하는 작업을 빼먹어으면 런타임시 에러가 수두룩 뜨곤 했었다. 근데 Hilt에서는 따로 추가하는 작업 없이 @InstallIn 어노테이션만 추가해주면 된다. 귀찮고 빼먹기 쉬운 코드를 확 줄일 수 있었다.

 

@Module
@InstallIn(ApplicationComponent::class)
class DatabaseModule {
    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "database")
            .build()
    }

 

이외에도 편리한 점이 더 많을텐데 사이드 프로젝트 규모가 크지 않아서 아직 다 경험하지 못한 것 같다... 앞으로 쓰다가 괜찮으면 추가로 정리해서 올려야지.

728x90

nodejs + postgresql

모바일/nodejs 2021. 1. 10. 13:12 Posted by 아는 개발자

nodejs로 postgresql 데이터베이스를 사용하는 방법. 엄청 간단하다. 

 

먼저 pg 라이브러리를 npm으로 설치한 후

 

npm install pg // pg library install

 

host 주소랑 포트번호 그리고 유저 정보들을 담은 오브젝트를 만든 후 pg client를 생성해 연결을 시켜준다.

 

const dbconfig = {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PW,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
    ssl: {
        rejectUnauthorized: false
    }
}

const client = new pg.Client(dbconfig)

client.connect(err => {
    if (err) {
        console.log('Failed to connect db ' + err)
    } else {
        console.log('Connect to db done!')
    }
})

 

정상적으로 연결이 완료 되면 선언한 pg client 객체를 이용해 db 쿼리를 날린다. 결과 값은 promise의 형태로도 받을 수 있는데 여기선 비동기 콜백을 피하고자 await로 받았다. 쿼리 결과 값은 리턴 객체의 rows 배열에 있으니 얘를 잘 써먹으면 된다.

 

rows() = () => client.query('select * from tb_table')

router.get('/api/v1/rows', async (req, res) => {
    try {
        const rowQuery = await rows();
        const resp = response.Builder.buildOkResponse({
            row: rowQuery.rows.map()
        })

 

 

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

cannot find module - heroku

모바일/삽질 기록 2021. 1. 10. 11:01 Posted by 아는 개발자

파일 이름을 리팩토링 한 후 새롭게 배포를 했더니 heroku에서 파일을 찾을 수 없다는 에러가 발생하게 됐다. 분명 로컬에서는 아무 문제 없이 제대로 돌아가고 있는데 heroku에 deploy하면 file을 찾을 수 없다는 에러가 발생했다.

 

2021-01-10T01:25:18.946380+00:00 app[web.1]: mytrot development mode
2021-01-10T01:25:19.123710+00:00 app[web.1]: internal/modules/cjs/loader.js:883
2021-01-10T01:25:19.123711+00:00 app[web.1]: throw err;
2021-01-10T01:25:19.123712+00:00 app[web.1]: ^
2021-01-10T01:25:19.123712+00:00 app[web.1]:
2021-01-10T01:25:19.123713+00:00 app[web.1]: Error: Cannot find module '../utils/errors'
2021-01-10T01:25:19.123713+00:00 app[web.1]: Require stack:
2021-01-10T01:25:19.123713+00:00 app[web.1]: - /app/lib/middleware/auth-checker.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: - /app/lib/routers/user.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: - /app/index.js
2021-01-10T01:25:19.123714+00:00 app[web.1]: at Function.Modu

 

하지만 아무리 눈 씻고 찾아봐도 내 프로젝트상의 코드에는 문제가 없다. 파일을 임포트 한 파일 명도 문제 없고 파일도 지정된 경로에 있다.

 

 

이럴때는 heroku app 의 터미널을 열어본다. heroku run bash 명령어로 나의 애플리케이션 프로젝트 폴더의 터미널을 열어 볼 수 있다. 문제가 된 파일이 있는 경로로 이동하니까 파일 이름이 Errors.js 라고 돼있었다. Errors.js는 errors.js 전에 설정한 파일 명이다. 파일 이름 변경사항이 heroku 프로젝트에 반영이 되지 않아서 그렇다.

 

user@kwony ~ % heroku run bash -a mytrot-dev
~ $ cd lib/utils/
~/lib/utils $ ls
Errors.js  jwt-utils.js  trot-response.js

 

heroku의 문제라기 보다는 mac + git의 문제라고 보는게 맞다. 문제가 된 파일(Errors.js, errors.js)은 대소문자가 바뀐 것 빼고는 변경 사항이 없는데 mac의 git에서는 이런 파일 이름의 변경사항을 놓치고 있다.

 

파일 이름을 index.js 에서 Index.js로 바꿨는데 git status에서는 변화 없는 것으로 나온다.

 

이럴때는 파일 이름을 대소문자만 바꾸는게 아니라 다른 변경 사항도 추가한다거나, 아니면 파일 이름을 한단계 더 거쳐서 바꾸거나 아니면 git mv 로 일일이 파일 이름을 바꾸면 된다. 번거롭고 실수하기도 쉬운 부분인데.. 왜 이렇게 만들어놨지

 

mv Errors.js errors-temp.js
mv errors-temp.js errors.js

----------------------------------

git mv -f OldFileNameCase newfilenamecase
728x90

'모바일 > 삽질 기록' 카테고리의 다른 글

cannot find module - heroku  (0) 2021.01.10
error: internal/modules/cjs/loader.js:883  (0) 2020.12.14
visual code definition 찾고 돌아가기  (0) 2020.12.13
RxJava: mapper function returned null 에러  (0) 2020.02.14
addr2line  (0) 2018.12.22
gcc로 pthread API 컴파일하기  (2) 2018.10.30

nodejs + multer 파일 업로드

모바일/nodejs 2021. 1. 7. 20:07 Posted by 아는 개발자

multer 라는 npm 라이브러리를 사용하면 nodejs로 쉽게 파일 업로드 api를 구축 할 수 있다. multer 라이브러리를 설치하고 내부 함수인 diskStorage 로 저장 받는 파일의 장소와 저장하게될 파일 이름을 설정하자. destination 필드에 파일 다운 받을 경로를 정하고 filename 필드에 다운 받는 파일 이름을 정한다. 그리고 정해둔 값으로 multer 오브젝트를 생성한다.

 

const multer = require('multer')
const path = require('path')

const storage = multer.diskStorage(
    {
        destination: './uploads',
        filename: function (req, file, cb) {
            cb(null, Date.now() + path.extname(file.originalname))
        }
    }
)

const upload = multer( { storage: storage } )

 

api 에서는 마지막에 생성한 multer 오브젝트를 middleware로 넣어주는데 파일 필드로 받을 key 값을 같이 넣어준다. 아래 api에서는 image라는 필드로 정했다. 이렇게 api를 설정하고 post 명령으로 호출하면 uploads 폴더에 파일이 생긴다.

 

router.post('/media/image', upload.single('image'), async(req, res) => {
    try {
        res.status(200).send()
    } catch (error) {
        console.log(error)
        res.status(400).send(error)
    }
})

 

 

728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24

nodejs + s3 upload/get

모바일/nodejs 2021. 1. 7. 19:56 Posted by 아는 개발자

nodejs로 AWS S3 스토리지에 업로드를 하기 위해선 먼저 S3 access 권한을 갖고 있는 IAM 사용자가 있어야 한다. 이것까지 설명하면 어려우니 아래 사진과 같은 권한을 가진 사용자가 필요하다는 것을 먼저 알아두자. 

 

 

사용자를 만들면 accessKeyId랑 secretAccessKey를 받는다. 이 string 값을 이용해 nodejs의 AWS S3 오브젝트를 만든다

 

const AWS = require('aws-sdk')

const s3 = new AWS.S3({
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
})

 

만든 객체로 s3 고유 함수를 호출 할 수 있다. 업로드의 인자에는 s3 버킷을 식별 할 수 있는 정보인 버킷 이름(Bucket), 권한 정보(ACL), 컨텐츠 타입 (ContentType), 보낼 파일 스트림 (Body), 파일 저장 경로(Key)를 입력한다. 아래 코드는 sync하게 호출하고자 promise() 함수까지 호출 했는데 원래는 비동기 함수라 .then, catch 함수도 호출이 가능하다.

 

const createUploadParam = (filePath) => {
    return {
        'Bucket': process.env.S3_BUCKET_NAME,
        'ACL': 'public-read',
        'ContentType': 'image/' + path.extname(filePath).substring(1),
        'Body': fs.createReadStream(filePath),
        'Key': moment().format("YYYY-MM-DD") + '/' + path.parse(filePath).base
    }
}

exports.uploadImage = async (filePath) => {
    return await s3.upload(createUploadParam(filePath)).promise()
}

 

파일 가져 올 때도 비슷하게 버킷의 정보가 필요하다. 단 이때는 버킷 이름과, 파일 경로만 있으면 된다. headObject 함수로 파일의 유무를 확인 한 후 있으면 getObject 함수로 파일에 대한 stream을 받을 수 있다

 

exports.createReadParam = (filePath) => {
    return {
        'Bucket': process.env.S3_BUCKET_NAME,
        'Key': filePath
    }
}

s.headObject(createReadParam(filePath)).on('success',() =>{
    s.getObject(param).createReadStream().pipe()
})
728x90

'모바일 > nodejs' 카테고리의 다른 글

nodejs + postgresql  (0) 2021.01.10
nodejs + multer 파일 업로드  (0) 2021.01.07
nodejs + s3 upload/get  (0) 2021.01.07
node-schedule-tz  (0) 2021.01.07
express  (0) 2020.12.24
debugger  (0) 2020.12.24