You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Android中仅使用Java实现MVI?求纯Java版MVI应用开发学习资源链接

Hey there! I’ve got you covered with both a step-by-step implementation of MVI (Model-View-Intent) in Android using pure Java, plus some solid learning resources focused on Java (no Kotlin required). Let’s dive in!

Implementing MVI in Android with Java

MVI’s core idea is unidirectional data flow: User actions (Intent) are processed, which updates the UI state (Model), and the View renders based solely on this state. Here’s how to build each component:

1. Intent: Encapsulate User Actions

Create an abstract class to represent all possible user interactions. Each specific action gets its own subclass:

// Base class for all user intents
public abstract class UserIntent {
    // Intent to trigger loading user data
    public static class LoadUserIntent extends UserIntent {
        private final int userId;

        public LoadUserIntent(int userId) {
            this.userId = userId;
        }

        public int getUserId() {
            return userId;
        }
    }

    // Intent to update the user's name
    public static class UpdateUserNameIntent extends UserIntent {
        private final String newName;

        public UpdateUserNameIntent(String newName) {
            this.newName = newName;
        }

        public String getNewName() {
            return newName;
        }
    }
}

2. Model: Single Source of UI State

This class represents the entire state of your screen (loading, success, error, and data). It’s immutable to avoid unexpected state changes:

public class UserState {
    public enum UiState {LOADING, SUCCESS, ERROR}

    private final UiState uiState;
    private final UserData userData;
    private final String errorMessage;

    // Private constructor to enforce state creation via static methods
    private UserState(UiState uiState, UserData userData, String errorMessage) {
        this.uiState = uiState;
        this.userData = userData;
        this.errorMessage = errorMessage;
    }

    // Factory methods for each state
    public static UserState loading() {
        return new UserState(UiState.LOADING, null, null);
    }

    public static UserState success(UserData userData) {
        return new UserState(UiState.SUCCESS, userData, null);
    }

    public static UserState error(String errorMessage) {
        return new UserState(UiState.ERROR, null, errorMessage);
    }

    // Getters (no setters to keep state immutable)
    public UiState getUiState() { return uiState; }
    public UserData getUserData() { return userData; }
    public String getErrorMessage() { return errorMessage; }

    // Helper class for user data
    public static class UserData {
        private final int id;
        private final String name;

        public UserData(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() { return id; }
        public String getName() { return name; }
    }
}

3. View: Render State & Send Intents

Your Activity/Fragment observes the state and triggers Intents based on user input:

public class UserActivity extends AppCompatActivity {
    private UserViewModel viewModel;
    private TextView userNameTv;
    private ProgressBar loadingPb;
    private TextView errorTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        initViews();
        initViewModel();

        // Trigger load user intent on button click
        findViewById(R.id.load_user_btn).setOnClickListener(v -> {
            viewModel.processIntent(new UserIntent.LoadUserIntent(1));
        });

        // Trigger update name intent on button click
        findViewById(R.id.update_name_btn).setOnClickListener(v -> {
            String newName = ((EditText) findViewById(R.id.name_input)).getText().toString();
            viewModel.processIntent(new UserIntent.UpdateUserNameIntent(newName));
        });
    }

    private void initViews() {
        userNameTv = findViewById(R.id.user_name_tv);
        loadingPb = findViewById(R.id.loading_pb);
        errorTv = findViewById(R.id.error_tv);
    }

    private void initViewModel() {
        viewModel = new ViewModelProvider(this).get(UserViewModel.class);
        viewModel.getStateLiveData().observe(this, this::renderState);
    }

    // Render UI based on current state
    private void renderState(UserState state) {
        switch (state.getUiState()) {
            case LOADING:
                loadingPb.setVisibility(View.VISIBLE);
                userNameTv.setVisibility(View.GONE);
                errorTv.setVisibility(View.GONE);
                break;
            case SUCCESS:
                loadingPb.setVisibility(View.GONE);
                errorTv.setVisibility(View.GONE);
                userNameTv.setVisibility(View.VISIBLE);
                userNameTv.setText(state.getUserData().getName());
                break;
            case ERROR:
                loadingPb.setVisibility(View.GONE);
                userNameTv.setVisibility(View.GONE);
                errorTv.setVisibility(View.VISIBLE);
                errorTv.setText(state.getErrorMessage());
                break;
        }
    }
}

4. ViewModel: Process Intents & Emit State

The ViewModel acts as the middleman: it receives Intents, interacts with data sources, and emits new states:

public class UserViewModel extends ViewModel {
    private final MutableLiveData<UserState> stateLiveData = new MutableLiveData<>();
    private final UserRepository repository;

    public UserViewModel() {
        repository = new UserRepository();
    }

    public LiveData<UserState> getStateLiveData() {
        return stateLiveData;
    }

    // Route intents to their handlers
    public void processIntent(UserIntent intent) {
        if (intent instanceof UserIntent.LoadUserIntent) {
            handleLoadUser((UserIntent.LoadUserIntent) intent);
        } else if (intent instanceof UserIntent.UpdateUserNameIntent) {
            handleUpdateUserName((UserIntent.UpdateUserNameIntent) intent);
        }
    }

    private void handleLoadUser(UserIntent.LoadUserIntent intent) {
        stateLiveData.postValue(UserState.loading());
        // Simulate async data fetch
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            try {
                UserState.UserData userData = repository.getUser(intent.getUserId());
                stateLiveData.postValue(UserState.success(userData));
            } catch (Exception e) {
                stateLiveData.postValue(UserState.error(e.getMessage()));
            }
        }, 1500);
    }

    private void handleUpdateUserName(UserIntent.UpdateUserNameIntent intent) {
        stateLiveData.postValue(UserState.loading());
        // Simulate async update
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            try {
                UserState.UserData updatedUser = repository.updateUserName(intent.getNewName());
                stateLiveData.postValue(UserState.success(updatedUser));
            } catch (Exception e) {
                stateLiveData.postValue(UserState.error(e.getMessage()));
            }
        }, 1000);
    }

    // Simulated data repository
    private static class UserRepository {
        public UserState.UserData getUser(int userId) throws Exception {
            if (userId == 1) {
                return new UserState.UserData(1, "John Doe");
            } else {
                throw new Exception("User not found");
            }
        }

        public UserState.UserData updateUserName(String newName) throws Exception {
            if (newName.isEmpty()) {
                throw new Exception("Name cannot be empty");
            }
            return new UserState.UserData(1, newName);
        }
    }
}
Java-Focused MVI Learning Resources

Since you’re avoiding Kotlin, here are reliable ways to learn MVI with Java:

  • Android Architecture Components Docs (Java Samples): The official Android docs include Java examples for LiveData and ViewModel—these are foundational for MVI. Look for sections on unidirectional data flow to connect the dots.
  • Java-Focused Android Architecture Books: Titles like Android Programming: The Big Nerd Ranch Guide (later chapters) and Clean Architecture for Android include Java implementations of pattern-based architectures, including MVI-adjacent designs.
  • GitHub Java MVI Projects: Search for "Android MVI Java" on GitHub—you’ll find full projects (like todo apps or news clients) that implement MVI purely in Java. Clone them to study how data flows and components interact.
  • YouTube Java MVI Tutorials: Search for "Android MVI Java tutorial" to find step-by-step video guides. Many developers walk through building MVI apps from scratch using only Java, which is great for hands-on learning.

内容的提问来源于stack exchange,提问作者Neo

火山引擎 最新活动