Android

Rxjava + MVVM + databinding

그란. 2018. 8. 21. 11:32

MainActivity


public class
MainActivity extends BaseActivity {

@NonNull
@Override
protected ViewModel createViewModel() {
return new MainViewModel(getNavigator());
}

@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
}



BaseActivity

public abstract class BaseActivity extends MvvmActivity {

@NonNull
protected Navigator getNavigator() {
return new Navigator() {
@Override
public void openDetailsPage(Item item) {
navigate(ItemDetailsActivity.class);
}

@Override
public void navigateToFunctionalDemo() {
navigate(DataLoadingActivity.class);
}

@Override
public void navigateToAdapterDemo() {
navigate(ItemListActivity.class);
}

@Override
public void navigateToTwoWayBindingDemo() {
navigate(SearchActivity.class);
}

@Override
public void navigateToCalculatorDemo() {
navigate(CalculatorActivity.class);
}

private void navigate(Class<?> destination) { // 화면 이동
Intent intent = new Intent(BaseActivity.this, destination);
startActivity(intent);
}
};
}

@NonNull
protected MessageHelper getMessageHelper() {
return new MessageHelper() {
@Override
public void show(String message) {
Toast.makeText(BaseActivity.this, message, Toast.LENGTH_SHORT).show();
}
};
}

}



MessageHelper 인터페이스

public interface MessageHelper {
void show(String message);
}



MvvmActivity

public abstract class MvvmActivity extends AppCompatActivity {
private ViewDataBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, getLayoutId());
getDefaultBinder().bind(binding, createViewModel());
}

@Override
protected void onDestroy() {
getDefaultBinder().bind(binding, null);
binding.executePendingBindings();
super.onDestroy();
}

@NonNull
private ViewModelBinder getDefaultBinder() {
ViewModelBinder defaultBinder = BindingUtils.getDefaultBinder();
Preconditions.checkNotNull(defaultBinder, "Default Binder");
return defaultBinder;
}

@NonNull
protected abstract ViewModel createViewModel();

@LayoutRes
protected abstract int getLayoutId();
}



ViewModelBinder 인터페이스

public interface ViewModelBinder {
void bind(ViewDataBinding viewDataBinding, ViewModel viewModel);
}



Preconditions 클래스

public class Preconditions {
public static <T> void checkNotNull(T value, String name) {
if (value == null) {
throw new NullPointerException(name + " should not be null");
}
}
}



ExampleApplication ( 기본 애플리케이션 )

public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}

private RefWatcher refWatcher;

@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
BindingUtils.setDefaultBinder(BindingAdapters.defaultBinder);
}
}




CalculatorActivity 로 이동했어


public class CalculatorActivity extends MvvmActivity {

@NonNull
@Override
protected ViewModel createViewModel() {
return new CalculatorViewModel();
}

@Override
protected int getLayoutId() {
return R.layout.activity_calculator;
}
}



<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="vm"
type="com.manaschaudhari.android_mvvm.sample.calculator_example.CalculatorViewModel" />
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
tools:context="com.manaschaudhari.android_mvvm.sample.calculator_example.CalculatorActivity">

<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Number 1"
android:inputType="number"
android:text="@={vm.number1}" />

<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Number 2"
android:inputType="number"
android:text="@={vm.number2}" />

<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkedButton="@={vm.operation}">

<RadioButton
android:id="@+id/radio_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+" />

<RadioButton
android:id="@+id/radio_subtract"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-" />

<RadioButton
android:id="@+id/radio_multiply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*" />

<RadioButton
android:id="@+id/radio_divide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/" />
</RadioGroup>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{vm.result}" />
</LinearLayout>

</layout>




CalculatorViewModel 에서 처리


public class CalculatorViewModel implements ViewModel {

public final ObservableField<String> number1 = new ObservableField<>("");
public final ObservableField<String> number2 = new ObservableField<>("");
public final ObservableField<Calculator.Operation> operation = new ObservableField<>(Calculator.Operation.ADD);

public final ReadOnlyField<String> result;

public CalculatorViewModel() {
final Calculator calculator = new Calculator();

Observable<String> result = Observable.combineLatest(
toObservable(number1), toObservable(number2), toObservable(operation),
new Function3<String, String, Calculator.Operation, String>() {
@Override
public String apply(String s1, String s2, Calculator.Operation operation) throws Exception {
try {
int n1 = Integer.parseInt(s1);
int n2 = Integer.parseInt(s2);

Integer result = calculator.run(n1, n2, operation);
return (result != null) ? result.toString() : "ERROR";

} catch (NumberFormatException e) {
return "";
}
}
});
this.result = toField(result);
}
}



Calculator 클래스

public class Calculator {

@Nullable
public Integer run(int a, int b, @NonNull Operation operation) {
switch (operation) {
case ADD:
return a + b;
case SUBTRACT:
return a - b;
case MULTIPLY:
return a * b;
case DIVIDE:
if (b != 0) {
return a / b;
} else {
return null;
}
}
return null;
}

public enum Operation {
ADD, SUBTRACT, MULTIPLY, DIVIDE
}
}




이렇게 하는 이유...

Rxjava : 일단 옵저버 패턴, 관측하고 있다가 변경사항이 있을 때 자동으로 메소드가 호출되는 방식(이 경우에는 run)


Mvvm : 클래스간의 종속성을 최소화 하고 (자바의 특징인 객체생성 및 사용) 모듈테스트가 편하다는 점인데


C#.NET 의 특징이긴하다. 근데 진짜 좋은지를 피부로 못느끼겠음.


한마디로 이런 방식은 안드로이드자바를 C#.NET의 WPF MVVM 패턴화 시킨 것


C#이 더 좋다는 뜻인데.