Android

[Java][Kotlin][Android] μ•ˆλ“œλ‘œμ΄λ“œ μžλ°”μ™€ μ½”ν‹€λ¦° ν•¨κ»˜ μ‚¬μš©ν•˜κΈ° - μ•ˆλ“œλ‘œμ΄λ“œ Legacy μ½”λ“œ μœ μ§€λ³΄μˆ˜ ν•˜κΈ° (1)

λŽμš” 2023. 5. 19. 12:43

μ•ˆλ…•ν•˜μ„Έμš” πŸ‘‹

거의 ν•œ 달 λ§Œμ— ν¬μŠ€νŒ…μœΌλ‘œ μ°Ύμ•„λ΅™λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

μ—¬λŸ¬ ν”„λ‘œμ νŠΈλ“€μ„ κ²½ν—˜ν•˜λ‹€ 보면 μžλ°”λ‘œ λ˜μ–΄μžˆλŠ” μ½”λ“œλ₯Ό μ½”ν‹€λ¦°μœΌλ‘œ λ°”κΎΈκ±°λ‚˜

λ ˆκ±°μ‹œ μ½”λ“œλ“€μ„ μœ μ§€ λ³΄μˆ˜ν•˜λ©΄μ„œ 코틀린을 ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” κ²½μš°κ°€ λ”λŸ¬ μžˆμŠ΅λ‹ˆλ‹€.

μ˜€λŠ˜μ€ 이런 κ²½μš°μ— λŒ€ν•œ μ—¬λŸ¬κ°€μ§€ λ‚΄μš©λ“€μ„ ν¬μŠ€νŒ…ν•΄λ³΄λ € ν•©λ‹ˆλ‹€.

이번 ν¬μŠ€νŒ…μ€ 총 2편으둜 기획이 될 κ²ƒμœΌλ‘œ 보이며 

1편(λ³Έ ν¬μŠ€νŒ…)은 μžλ°”μ™€ 코틀린을 ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” 방법

2νŽΈμ€ μžλ°” μ½”λ“œλ₯Ό μ½”ν‹€λ¦°μœΌλ‘œ λ³€κ²½ν•˜λŠ” 방법에 λŒ€ν•΄μ„œ ν¬μŠ€νŒ… 해보렀 ν•©λ‹ˆλ‹€!

 

그럼 λ°”λ‘œ μ‹œμž‘ν•΄λ³΄λ„λ‘ ν• κΉŒμš”? 😎


1. μ„€μ • 

μžλ°” 기반 ν”„λ‘œμ νŠΈμ—μ„œ 코틀린을 μ‚¬μš©ν•˜κΈ° μœ„ν•΄ μœ„ μ½”λ“œμ™€ 같이 build.gradle을 μ„€μ •ν•΄μ€λ‹ˆλ‹€. 

// Project gradle
plugins {
    ... // μƒλž΅
    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
}
// Module gradle
plugins {
	... // μƒλž΅
    id 'org.jetbrains.kotlin.android'
}

android {
	... // μƒλž΅
    
    sourceSets{
        main.java.srcDirs+='src/main/kotlin'
    }
}

dependencies {
    ... // μƒλž΅
    
    // Kotlin
    implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10'
}

 

 

2. 디렉토리 생성

Android Studioμ—μ„œ ν”„λ‘œμ νŠΈ 보기 섀정을 Android -> Project둜 λ³€κ²½ ν›„ main에 kotlinμ΄λΌλŠ” μ΄λ¦„μœΌλ‘œ 디렉토리λ₯Ό 생성해 μ€λ‹ˆλ‹€.

 

디렉토리 생성

μš°μ„  기본적인 섀정은 μ΄λ ‡κ²Œ 마치면 λ˜κ² μŠ΅λ‹ˆλ‹€. 🀩

그럼 μ§€κΈˆλΆ€ν„°λŠ” 아직 μžλ°”λ₯Ό μ“°κ³  μžˆλŠ” ν”„λ‘œμ νŠΈμ— μ—¬λŸ¬ κ°€μ§€ 라이브러리 및 νŒ¨ν„΄ 등을 μ μš©μ‹œν‚€λŠ” 방법을 μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.


· MainActivity

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private ActivityMainBinding mBinding;
    private MainViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this , R.layout.activity_main);
        mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
        mViewModel.callAllBeachList();
        observeData();
    }

    private void observeData() {
        mViewModel.getToastMessage().observe(this, new Observer<String>() { // Toast Message
            @Override
            public void onChanged(String message) {
                Toast.makeText(MainActivity.this, message , Toast.LENGTH_SHORT).show();
            }
        });

        mViewModel.getBeachList().observe(this, new Observer<List<BeachDTO>>() {
            @Override
            public void onChanged(List<BeachDTO> beachDTOs) {
                for(BeachDTO beach : beachDTOs){
                    Log.d(TAG, "onChanged: " + beach.toString());
                }
            }
        });
    }
}

 

· MainViewModel

@HiltViewModel
public class MainViewModel extends ViewModel {
    private BeachRepository mBeachRepository;
    private CompositeDisposable mCompositeDisposable;

    private MutableLiveData<List<BeachDTO>> mBeachList = new MutableLiveData<>();
    public LiveData<List<BeachDTO>> getBeachList() {
        return mBeachList;
    }

    private MutableLiveData<String> mToastMessage = new MutableLiveData<>();
    public void setToastMessage(String message) {
        mToastMessage.setValue(message);
    }
    public MutableLiveData<String> getToastMessage() {
        return mToastMessage;
    }

    @Inject
    MainViewModel(BeachRepository repository){
        mBeachRepository = repository;
    }

    /**
     * RxJavaλ₯Ό 톡해 ν•΄μˆ˜μš•μž₯ ν˜Όμž‘λ„λ₯Ό κ°€μ Έμ˜€λŠ” λ©”μ†Œλ“œ
     * **/
    public void callAllBeachList() {
        if(mCompositeDisposable == null){
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(
                mBeachRepository.getBeachCongestion()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeWith(new DisposableSingleObserver<BeachListDTO>(){
                            @Override
                            public void onSuccess(@NonNull BeachListDTO beachListDTO) {
                                mBeachList.setValue(beachListDTO.getAllBeachList());
                            }

                            @Override
                            public void onError(@NonNull Throwable e) {
                                mToastMessage.setValue("톡신에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€!");
                            }
                        })
        );
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if(mCompositeDisposable != null){
            mCompositeDisposable.clear(); // ViewModel이 clear λ λ•Œ CompositeDisposable μ‚­μ œ
            mCompositeDisposable = null;
        }
    }
}

 

μœ„ λ³΄μ‹œλŠ” 바와 같이 MainActivityλŠ” Java기반으둜 RxJava와 LiveDataλ₯Ό 톡해 ν†΅μ‹ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

ν•΄λ‹Ή Activity와 ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” Repository와 Hilt의 module , Modelμ—μ„œ μ‚¬μš©ν•  class 등을 μ½”ν‹€λ¦°μœΌλ‘œ

μž‘μ„±ν•΄ ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

 

· RestApi (Retrofit API 톡신을 μœ„ν•œ Interface)

interface RestApi {
    @GET(Utils.GET_BEACH_CONGESTION_URL)
    fun getBeachCongestion() : Single<BeachListDTO>
}

 

· BeachRepository ( Repository Interface RxJava와 ν•¨κ»˜ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ Single 객체λ₯Ό μ „λ‹¬λ°›λŠ”λ‹€. )

interface BeachRepository {
    /**
     * ν•΄μˆ˜μš•μž₯ ν˜Όμž‘λ„ κ°€μ Έμ˜€κΈ°
     * **/
    fun getBeachCongestion() : Single<BeachListDTO>
}

 

· BeachRepositoryImpl (Repository의 κ΅¬ν˜„μ²΄ )

class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * ν•΄μˆ˜μš•μž₯ ν˜Όμž‘λ„ κ°€μ Έμ˜€κΈ°
     * **/
    override fun getBeachCongestion(): Single<BeachListDTO> {
        return restApi.getBeachCongestion()
    }
}

 

· Modelμ—μ„œ μ‚¬μš©ν•  DTO classλ“€

data class BeachDTO(
    val etlDt : String = "",
    val seqId : Int = -1,
    val poiNm : String = "",
    val uniqPop : Int = -1,
    var congestion : String = ""
)
class BeachListDTO {
    private lateinit var mBeachList : MutableList<BeachDTO>

    @SerializedName("Beach0")
    val beach0 : BeachDTO? = null
    @SerializedName("Beach1")
    val beach1 : BeachDTO? = null
    @SerializedName("Beach2")
    ... μƒλž΅

    fun getAllBeachList() : MutableList<BeachDTO> {
        if(!::mBeachList.isInitialized){
            mBeachList = mutableListOf()
            mBeachList.add(beach0 as BeachDTO)
            mBeachList.add(beach1 as BeachDTO)
            mBeachList.add(beach2 as BeachDTO)
            ... μƒλž΅
        }
       return mBeachList
    }
}

 

· ProvideModule( Providesλ₯Ό 해쀄 μš”μ†Œλ“€μ„ λͺ¨μ•„놓은 Module )

@Module
@InstallIn(SingletonComponent::class)
object ProvideModule {
    @Provides
    @Singleton
    fun provideRestApi(okHttpClient: OkHttpClient) : RestApi {
        return Retrofit.Builder()
            .baseUrl(Utils.BEACH_BASE_URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(RestApi::class.java)
    }

    @Provides
    @Singleton
    fun provideOkHttpClient() : OkHttpClient {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level= HttpLoggingInterceptor.Level.BODY

        return OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .connectTimeout(Utils.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
            .build()
    }
}

 

· BindModule( Bindsλ₯Ό 해쀄 μš”μ†Œλ“€μ„ λͺ¨μ•„놓은 Module )

@Module
@InstallIn(SingletonComponent::class)
abstract class BindModule {
    @Binds
    @Singleton
    abstract fun bindBeachRepository(beachRepositoryImpl: BeachRepositoryImpl) : BeachRepository
}

μœ„μ—μ„œ 보이듯이 Hilt 및 μ—¬λŸ¬ κ°€μ§€ Repository 등은 μ½”ν‹€λ¦°μœΌλ‘œ μƒˆλ‘œ μΆ”κ°€ν•˜μ—¬ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ• μ΄ˆμ— μ½”ν‹€λ¦°μ˜ μž₯점 쀑 ν•˜λ‚˜κ°€ μžλ°”μ™€ ν˜Έν™˜μ΄ λœλ‹€λŠ” 것이기 λ•Œλ¬Έμ— μ΄λ ‡κ²Œ μ‚¬μš©ν•˜λŠ” 것은 λ‹Ήμ—°ν•  ν…λ°μš”

였래된 ν”„λ‘œμ νŠΈλ“€μ„ μœ μ§€ 보수 ν•˜λ‹€ 보면 μžλ°”λ‘œ 된 뢀뢄은 μžλ°”λ‘œ μœ μ§€λ³΄μˆ˜λ₯Ό μ§„ν–‰ν•˜κ³ 

μ‹ κ·œ κΈ°λŠ₯λΆ€ν„°λŠ” 코틀린을 μ μš©ν•˜μ—¬ κ°œλ°œν•˜λŠ” κ²½μš°κ°€ λ§Žμ€λ° μ–΄λ–»κ²Œ μ‹œμž‘μ„ ν•΄μ•Ό ν• μ§€ 감이 μ•ˆ μž‘νžˆλŠ” κ²½μš°κ°€ λ§ŽμŠ΅λ‹ˆλ‹€.

그런 λΆ„λ“€μ—κ²Œ μ‘°κΈˆμ΄λ‚˜λ§ˆ 도움이 λ˜μ—ˆμœΌλ©΄ μ’‹κ² μŠ΅λ‹ˆλ‹€.  πŸ™

 

λ‹€μŒ ν¬μŠ€νŒ…μœΌλ‘œλŠ” ν•΄λ‹Ή μžλ°” μ½”λ“œλ₯Ό μ–΄λ–»κ²Œ μ½”ν‹€λ¦°μœΌλ‘œ Migration ν•˜λŠ”μ§€μ— λŒ€ν•΄ λ‹€λ€„λ³΄κ² μŠ΅λ‹ˆλ‹€.

그럼

μ˜€λŠ˜λ„ μ¦μ½”ν•˜μ„Έμš” :)