๊พธ์ค€ํ•จ์ด ์ง„๋ฆฌ๋‹ค!!

์–ด์ œ๋ณด๋‹ค ๋ฐœ์ „ํ•œ ์˜ค๋Š˜์ด ๋˜๊ณ ํ”ˆ ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์˜ ๋ธ”๋กœ๊ทธ

Android

[Java][Kotlin][Android] ์•ˆ๋“œ๋กœ์ด๋“œ ์ž๋ฐ”์™€ ์ฝ”ํ‹€๋ฆฐ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ - ์•ˆ๋“œ๋กœ์ด๋“œ Legacy ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜ ํ•˜๊ธฐ (2)

๋ށ์š” 2023. 5. 30. 15:47

์•ˆ๋…•ํ•˜์„ธ์š” ๐Ÿ‘‹

์˜ค๋Š˜์€ ์ €๋ฒˆ ํฌ์ŠคํŒ…์— ์ด์–ด์„œ Legacy ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜ ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ˜Ž

์˜ค๋Š˜ ํฌ์ŠคํŒ…ํ•  ๋‚ด์šฉ์€ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ…ํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”ํ‹€๋ฆฐ๊ณผ ์ž๋ฐ”๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ๋Š” ์ด์ „ ํฌ์ŠคํŒ…์„ ์ฐธ๊ณ  ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

 

[Java][Kotlin][Android] ์•ˆ๋“œ๋กœ์ด๋“œ ์ž๋ฐ”์™€ ์ฝ”ํ‹€๋ฆฐ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ - ์•ˆ๋“œ๋กœ์ด๋“œ Legacy ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜ ํ•˜

์•ˆ๋…•ํ•˜์„ธ์š” ๐Ÿ‘‹ ๊ฑฐ์˜ ํ•œ ๋‹ฌ ๋งŒ์— ํฌ์ŠคํŒ…์œผ๋กœ ์ฐพ์•„๋ต™๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ๋“ค์„ ๊ฒฝํ—˜ํ•˜๋‹ค ๋ณด๋ฉด ์ž๋ฐ”๋กœ ๋˜์–ด์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋“ค์„ ์œ ์ง€ ๋ณด์ˆ˜ํ•˜๋ฉด์„œ ์ฝ”ํ‹€๋ฆฐ์„

devyo-111commit.tistory.com


Java ํŒŒ์ผ Kotlin์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ

์•„๋ž˜์™€ ๊ฐ™์ด ์›ํ•˜๋Š” ํŒŒ์ผ์„ 'Convert Java File to Kotlin File'์„ ํ†ตํ•ด ๋ณ€ํ™˜ํ•ด ์ค๋‹ˆ๋‹ค.

 

์ฝ”ํ‹€๋ฆฐ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•

 

์ž๋™์œผ๋กœ ๋ณ€ํ™˜์ด ์™„๋ฃŒ๋œ ํ›„ ์•„๋ž˜์™€ ๊ฐ™์€ ํŒ์—…์ด ๋‚˜ํƒ€๋‚˜๋Š”๋ฐ ๊ทธ๋Œ€๋กœ ์ง„ํ–‰์‹œ์ผœ ์ค๋‹ˆ๋‹ค. (ํ•œ๋งˆ๋””๋กœ ๋” ์ข‹์€ ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ ๋ณ€ํ•œ ์‹œ์ผœ ์ค„๊นŒ?)

 

 

์ด๋Ÿฌ๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ Java์ฝ”๋“œ๊ฐ€ Kotlin์ฝ”๋“œ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๊ฒƒ์„ ๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ๐Ÿคฉ

 

์ด๋ ‡๊ฒŒ๋งŒ ๋๋‚˜๋ฉด ์žฌ๋ฏธ์—†์œผ๋‹ˆ RxJava๋กœ ๋˜์–ด์žˆ๋˜ ๋ถ€๋ถ„์„ Coroutine์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…๊นŒ์ง€ ํ•จ๊ป˜ ์ง„ํ–‰ํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

๊ธฐ์กด

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

// BeachRepository.kt
interface BeachRepository {
    /**
     * ํ•ด์ˆ˜์š•์žฅ ํ˜ผ์žก๋„ ๊ฐ€์ ธ์˜ค๊ธฐ
     * **/
    fun getBeachCongestion() : Single<BeachListDTO>
}

// BeachRepositoryImpl.kt
class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * ํ•ด์ˆ˜์š•์žฅ ํ˜ผ์žก๋„ ๊ฐ€์ ธ์˜ค๊ธฐ
     * **/
    override fun getBeachCongestion(): Single<BeachListDTO> {
        return restApi.getBeachCongestion()
    }
}

 

๊ธฐ์กด ์ฝ”๋“œ๋Š” Rx๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด Single๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๋ถ€๋ถ„์„ ํ•จ๊ป˜ ๋ฐ”๊ฟ”๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋ณ€๊ฒฝ ํ›„

// RestApi.kt
interface RestApi {
    @GET(Utils.GET_BEACH_CONGESTION_URL)
    suspend fun getBeachCongestion() : Response<BeachListDTO>
}

// BeachRepository.kt
interface BeachRepository {
    /**
     * ํ•ด์ˆ˜์š•์žฅ ํ˜ผ์žก๋„ ๊ฐ€์ ธ์˜ค๊ธฐ
     * **/
    suspend fun getBeachCongestion() : Flow<NetworkResult<BeachListDTO>>
}

// BeachRepositoryImpl.kt
class BeachRepositoryImpl @Inject constructor(
    private val restApi: RestApi
) : BeachRepository {
    /**
     * ํ•ด์ˆ˜์š•์žฅ ํ˜ผ์žก๋„ ๊ฐ€์ ธ์˜ค๊ธฐ
     * **/
    override suspend fun getBeachCongestion(): Flow<NetworkResult<BeachListDTO>> {
        val response = restApi.getBeachCongestion()
        return if(response.isSuccessful) {
            flow{
                emit(NetworkResult.Success(response.body()!!))
            }
        } else {
            flow{
                emit(NetworkResult.Failure("๋„คํŠธ์›Œํฌ ํ†ต์‹ ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค!"))
            }
        }
    }
}

 

์œ„์ฒ˜๋Ÿผ Single ๊ฐ์ฒด๋ฅผ ๊ฑท์–ด๋‚ด๊ณ  Flow๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด์–ด์„œ ViewModel๊ณผ Activity์ฝ”๋“œ๋ฅผ ๋ฐ”๊ฟ”๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๊ธฐ์กด

@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;
        }
    }
}

 

๋ณ€๊ฒฝ ํ›„

sealed class NetworkResult<T> {
    data class Loading<T>(val isLoading : Boolean) : NetworkResult<T>()
    data class Success<T>(val data : T) : NetworkResult<T>()
    data class Failure<T>(val errorMessage : String) : NetworkResult<T>()
}
class MainViewModel @Inject constructor(
    private val beachRepository : BeachRepository
    ) : ViewModel() {
    private val _beachList = MutableStateFlow<List<BeachDTO>>(emptyList())
    val beachList: StateFlow<List<BeachDTO>>
    get() = _beachList

    /**
     * Coroutine๊ณผ Flow๋ฅผ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ•˜๋Š” ํ•จ์ˆ˜
     */
    fun callAllBeachList() {
        viewModelScope.launch {
            beachRepository.getBeachCongestion().collect{ result ->
                when(result){
                    is NetworkResult.Success -> {
                        _beachList.value = result.data.getAllBeachList()
                    }

                    is NetworkResult.Failure -> {
                        Log.d(TAG, "callAllBeachList: ${result.errorMessage}")
                    }

                    is NetworkResult.Loading -> {
                        Log.d(TAG, "callAllBeachList: Loading...")
                    }
                }
            }
        }
    }
}

Rx๋ฅผ ๊ฑท์–ด๋‚ด๊ณ  ํ•ด๋‹น ๋ถ€๋ถ„์„ Corouine์„ ํ†ตํ•ด ๊ฐ’์„ ์ˆ˜์‹ ํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์•„์šธ๋Ÿฌ, LiveData๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ๋ถ€๋ถ„๋„ StateFlow๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค :)

 

๊ธฐ์กด

@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());
                }
            }
        });
    }
}

 

๋ณ€๊ฒฝ ํ›„

private const val TAG = "MainActivity"

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private var binding: ActivityMainBinding? = null
    private val viewModel : MainViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        viewModel.callAllBeachList()
        observeData()
    }

    private fun observeData() {
       with(viewModel){
           lifecycleScope.launch{
               repeatOnLifecycle(Lifecycle.State.STARTED){
                   launch {
                       beachList.collect{ list ->
                           if(list.isNotEmpty()){
                               Log.d(TAG, "observeData: $list")
                           }
                       }
                   }
               }
           }
       }
    }
}

๋งˆ์ง€๋ง‰์œผ๋กœ Activity ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.


๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ์ ์šฉํ•ด ๋ณธ ๊ฒƒ์ด์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๊ฐ„๊ฒฐํ•ด์ง€๋Š” ๊ฒƒ์ด ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„๋“ค์˜ ์ƒ๊ฐ์€ ์–ด๋– ์‹ ๊ฐ€์š”? ๐Ÿค”

 

์ด์ƒ์œผ๋กœ ํฌ์ŠคํŒ…์„ ๋งˆ์น˜๋ฉฐ

Legacy์ฝ”๋“œ๋ฅผ ์œ ์ง€ ๋ณด์ˆ˜ํ•˜๋Š” ์—ฌ๋Ÿฌ๋ถ„๋“ค์—๊ฒŒ ๋งŽ์€ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์˜ค๋Š˜๋„ ์ฆ์ฝ”ํ•˜์„ธ์š” :)