如何在 Android Studio 的登录活动模板中实现 AsyncTask

Posted

技术标签:

【中文标题】如何在 Android Studio 的登录活动模板中实现 AsyncTask【英文标题】:How to implement AsyncTask in Login Activity template from Android Studio 【发布时间】:2019-09-11 05:07:14 【问题描述】:

我想在我的 android 应用中实现一个登录活动,我使用 Android Studio 模板进行登录活动。 根据这里的谷歌文档:https://developer.android.com/studio/projects/templates#LoginActivity 它应该包含一个我可以用于我的目的的 AsyncTask,但是当前版本的 Android Studio 似乎不再提供这个。

我使用的当前 android studio 版本是 3.4(2019 年 4 月 10 日),我的 sdk 是:

        minSdkVersion 23
        targetSdkVersion 26

我有一个带有以下代码的LoginDataSource.java 类,我认为这些代码将用于身份验证工作。但是,当我在 TODO 行下方调用我的 HTTP 方法时,它给了我一个关于无法在主线程中执行异步的异常:

/**
 * Class that handles authentication w/ login credentials and retrieves user information.
 */
public class LoginDataSource 

    public Result<LoggedInUser> login(String username, String password) 

        try 
            // TODO: handle loggedInUser authentication
            LoggedInUser fakeUser =
                    new LoggedInUser(
                            java.util.UUID.randomUUID().toString(),
                            "Jane Doe");
            return new Result.Success<>(fakeUser);
         catch (Exception e) 
            return new Result.Error(new IOException("Error logging in", e));
        
    

    public void logout() 
        // TODO: revoke authentication
    

我在之前的项目中使用 asynctask 实现了一个 HTTP 调用,但它在使用它的同一个活动中,而不是使用这个模板。 哪里应该是放置 asynctask 并调用它的最佳位置?

谢谢!

模板中的其他类:

LoginRepository.java

/**
 * Class that requests authentication and user information from the remote data source and
 * maintains an in-memory cache of login status and user credentials information.
 */
public class LoginRepository 

    private static volatile LoginRepository instance;

    private LoginDataSource dataSource;

    // If user credentials will be cached in local storage, it is recommended it be encrypted
    // @see https://developer.android.com/training/articles/keystore
    private LoggedInUser user = null;

    // private constructor : singleton access
    private LoginRepository(LoginDataSource dataSource) 
        this.dataSource = dataSource;
    

    public static LoginRepository getInstance(LoginDataSource dataSource) 
        if (instance == null) 
            instance = new LoginRepository(dataSource);
        
        return instance;
    

    public boolean isLoggedIn() 
        return user != null;
    

    public void logout() 
        user = null;
        dataSource.logout();
    

    private void setLoggedInUser(LoggedInUser user) 
        this.user = user;
        // If user credentials will be cached in local storage, it is recommended it be encrypted
        // @see https://developer.android.com/training/articles/keystore
    

    public Result<LoggedInUser> login(String username, String password) 
        // handle login
        Result<LoggedInUser> result = dataSource.login(username, password);
        if (result instanceof Result.Success) 
            setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
        
        return result;
    

LoggedInUserView.java

/**
 * Class exposing authenticated user details to the UI.
 */
class LoggedInUserView implements Serializable 
    private String displayName;
    //... other data fields that may be accessible to the UI

    LoggedInUserView(String displayName) 
        this.displayName = displayName;
    

    String getDisplayName() 
        return displayName;
    

LoginViewModel.java

public class LoginViewModel extends ViewModel 

    private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
    private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
    private LoginRepository loginRepository;

    LoginViewModel(LoginRepository loginRepository) 
        this.loginRepository = loginRepository;
    

    LiveData<LoginFormState> getLoginFormState() 
        return loginFormState;
    

    LiveData<LoginResult> getLoginResult() 
        return loginResult;
    

    public void login(String username, String password) 
        // can be launched in a separate asynchronous job
        Result<LoggedInUser> result = loginRepository.login(username, password);

        if (result instanceof Result.Success) 
            LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
            loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));
         else 
            loginResult.setValue(new LoginResult(R.string.login_failed));
        
    

    public void loginDataChanged(String username, String password) 
        if (!isUserNameValid(username)) 
            loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
         else if (!isPasswordValid(password)) 
            loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
         else 
            loginFormState.setValue(new LoginFormState(true));
        
    

    // A placeholder username validation check
    private boolean isUserNameValid(String username) 
        if (username == null) 
            return false;
        
        if (username.contains("@")) 
            return Patterns.EMAIL_ADDRESS.matcher(username).matches();
         else 
            return !username.trim().isEmpty();
        
    

    // A placeholder password validation check
    private boolean isPasswordValid(String password) 
        return password != null && password.trim().length() > 5;
    

LoginActivity.java

public class LoginActivity extends AppCompatActivity 

    private LoginViewModel loginViewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory())
                .get(LoginViewModel.class);

        final EditText usernameEditText = findViewById(R.id.username);
        final EditText passwordEditText = findViewById(R.id.password);
        final Button loginButton = findViewById(R.id.login);
        final ProgressBar loadingProgressBar = findViewById(R.id.loading);

        loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() 
            @Override
            public void onChanged(@Nullable LoginFormState loginFormState) 
                if (loginFormState == null) 
                    return;
                
                loginButton.setEnabled(loginFormState.isDataValid());
                if (loginFormState.getUsernameError() != null) 
                    usernameEditText.setError(getString(loginFormState.getUsernameError()));
                
                if (loginFormState.getPasswordError() != null) 
                    passwordEditText.setError(getString(loginFormState.getPasswordError()));
                
            
        );

        loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() 
            @Override
            public void onChanged(@Nullable LoginResult loginResult) 
                if (loginResult == null) 
                    return;
                
                loadingProgressBar.setVisibility(View.GONE);
                if (loginResult.getError() != null) 
                    showLoginFailed(loginResult.getError());
                
                if (loginResult.getSuccess() != null) 
                    updateUiWithUser(loginResult.getSuccess());
                
                setResult(Activity.RESULT_OK);

                //Complete and destroy login activity once successful
                finish();
            
        );

        TextWatcher afterTextChangedListener = new TextWatcher() 
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) 
                // ignore
            

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) 
                // ignore
            

            @Override
            public void afterTextChanged(Editable s) 
                loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
                        passwordEditText.getText().toString());
            
        ;
        usernameEditText.addTextChangedListener(afterTextChangedListener);
        passwordEditText.addTextChangedListener(afterTextChangedListener);
        passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() 

            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) 
                if (actionId == EditorInfo.IME_ACTION_DONE) 
                    loginViewModel.login(usernameEditText.getText().toString(),
                            passwordEditText.getText().toString());
                
                return false;
            
        );

        loginButton.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                loadingProgressBar.setVisibility(View.VISIBLE);
                loginViewModel.login(usernameEditText.getText().toString(),
                        passwordEditText.getText().toString());
            
        );
    

    private void updateUiWithUser(LoggedInUserView model) 
        String welcome = getString(R.string.welcome) + model.getDisplayName();
        // TODO : initiate successful logged in experience
        Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
    

    private void showLoginFailed(@StringRes Integer errorString) 
        Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
    

【问题讨论】:

我认为您需要查看一些异步示例。这是一个。 ***.com/a/9671602/10936389 我知道如何实现异步任务。我的问题将更多关于在此模板中实现它的 where 。因为我无法通过在 // TODO: 中调用 .execute() 来使其工作:handle loggedInUser authentication 您的问题仍然与代码有关,因此与 IDE 无关。从哪里获得代码无关紧要 - 例如,如果您在 GH 上找到它,这并不意味着您可以标记问题 github - 这是一个无关紧要的标记。 你解决了这个问题吗?我处于同样的位置,对 MVVM 模式有点迷失。 我也在等待答复。 【参考方案1】:

我还在寻找一个好的解决方案。目前我最终实现了 2 个 Listeners 接口。

public interface OnLoginListener 
    void onLoginResult(Result result);

public interface OnRegistrationListener 
    void onRegistrationResult(Result result);

我让我的 ViewModel 实现了这些接口并相应地更改了 UI,我不确定这是否遵循 MVVM 模式的最佳实践。 LoginDataSource 被传递给监听器的引用

private OnLoginListener onLoginListener;
private OnRegistrationListener onRegistrationListener;

并从 onPostExecute 调用 onRegistrationResult 方法,或者在我的例子中从 Firebase 身份验证的 onComplete 方法调用。

@Override
        public void onComplete(@NonNull Task<AuthResult> task) 
            if (task.isSuccessful()) 
                // Sign in success, update UI with the signed-in user's information
                Log.d(TAG, "createUserWithEmail:success");
                FirebaseUser user = mAuth.getCurrentUser();
                onRegistrationListener.onRegistrationResult(new Result.Success<>(user));
             else 
                // If sign in fails, display a message to the user.
                Log.w(TAG, "createUserWithEmail:failure", task.getException());
                onRegistrationListener.onRegistrationResult(new Result.Error(task.getException()));
            
        

我希望这对某人有帮助;)。

【讨论】:

【参考方案2】:

考虑添加

new LoginTask().execute();

在try语句内,然后在login方法外添加内部类

public class LoginTask extends AsyncTask<Void, String, Void>
 RestTemplate restTemplate = new RestTemplate();
 ...

【讨论】:

【参考方案3】:

LoginViewModel 中使用 kotlinx.coroutines:

import kotlinx.coroutines.*

fun login(username: String, password: String) 
    // launched in a separate asynchronous job
    MainScope().launch 
        val result = withContext(Dispatchers.IO) 
            loginRepository.login(username, password)
        
        if (result is Result.Success) 
            _loginResult.value =
                LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
         else 
            _loginResult.value = LoginResult(error = R.string.login_failed)
        
    

LoginRepositoryLoginDataSource 中将关键字 suspend 添加到 fun login() 方法。

Handle kotlin coroutine 在 LoginDataSource 例如suspendCoroutine

添加build.gradle依赖:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_version"

【讨论】:

【参考方案4】:

确保一切正常。

最好的地方是 LoginViewModel.kt

这是示例代码。


    package com.example.gosoft.ui.login

import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import android.util.Patterns
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import com.example.gosoft.data.LoginRepository
import com.example.gosoft.data.Result

import com.example.gosoft.R
import org.json.JSONObject

class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() 

    private val _loginForm = MutableLiveData<LoginFormState>()
    val loginFormState: LiveData<LoginFormState> = _loginForm

    private val _loginResult = MutableLiveData<LoginResult>()
    val loginResult: LiveData<LoginResult> = _loginResult

    fun login(username: String, password: String, context: Context) 
        // can be launched in a separate asynchronous job

        //http request start

        val queue = Volley.newRequestQueue(context)



        val url = "Your URL"
        var name = "test"
        Log.d("name_init",name)

// Request a string response from the provided URL.
        val JsonObjectRequest =  JsonObjectRequest(
            Request.Method.GET, url, null,
            Response.Listener<JSONObject>  response ->
                // Display the first 500 characters of the response string.
                //textView.text = "Response is: $response.substring(0, 500)"
                name = "Aram"
                Log.d("name",name)
                Log.d("Response", response.toString())
                val result = loginRepository.login(username, password, name)
                if (result is Result.Success) 
                    _loginResult.value = LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
                 else 
                    _loginResult.value = LoginResult(error = R.string.login_failed)
                

            ,
            Response.ErrorListener  error ->
                name = "No name"
                Log.d("name_error",name)
                Log.d("volley_error", error.toString())
            )

// Add the request to the RequestQueue.
        Log.d("queue_add",name)
        queue.add(JsonObjectRequest)

        //http request end



//        val result = loginRepository.login(username, password)

//        if (result is Result.Success) 
//            _loginResult.value = LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
//         else 
//            _loginResult.value = LoginResult(error = R.string.login_failed)
//        
    

    fun loginDataChanged(username: String, password: String) 
        if (!isUserNameValid(username)) 
            _loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
         else if (!isPasswordValid(password)) 
            _loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
         else 
            _loginForm.value = LoginFormState(isDataValid = true)
        
    

    // A placeholder username validation check
    private fun isUserNameValid(username: String): Boolean 
        return if (username.contains('@')) 
            Patterns.EMAIL_ADDRESS.matcher(username).matches()
         else 
            username.isNotBlank()
        
    

    // A placeholder password validation check
    private fun isPasswordValid(password: String): Boolean 
        return password.length > 5;
    


【讨论】:

【参考方案5】:

我的回答有点晚了,但我就是在这种情况下。我在 LoginDataSource 对象中实现了异步任务:

/**
 * Class that handles authentication w/ login credentials and retrieves user information.
 */
public class LoginDataSource 
    LoggedInUser fakeUser;

    public Result<LoggedInUser> login(String username, String password) 

        try 
            LoginTask loginTask = new LoginTask();
            synchronized (loginTask) 
                loginTask.execute(map).notify();
            
            if (fakeUser != null) 
                return new Result.Success<>(fakeUser);
             else 
                return new Result.Error(new IOException("Usuario o contraseña incorrectos")); // closed hanging quotation
            
         catch (Exception e) 
            return new Result.Error(new IOException("Error logging in", e));
        
    

    public void logout() 
        // TODO: revoke authentication
    

    private class LoginTask extends AsyncTask<HashMap<String, String>, Void,  Void> 

        /**
         * Override this method to perform a computation on a background thread. The
         * specified parameters are the parameters passed to @link #execute
         * by the caller of this task.
         * <p>
         * This will normally run on a background thread. But to better
         * support testing frameworks, it is recommended that this also tolerates
         * direct execution on the foreground thread, as part of the @link #execute call.
         * <p>
         * This method can call @link #publishProgress to publish updates
         * on the UI thread.
         *
         * @param hashMaps The parameters of the task.
         * @return A result, defined by the subclass of this task.
         * @see #onPreExecute()
         * @see #onPostExecute
         * @see #publishProgress
         */
        @Override
        protected Void doInBackground(HashMap<String, String>... hashMaps) 
            
            // Use retrofit2 or whatever to get it
            fakeUser =
                    new LoggedInUser(
                            java.util.UUID.randomUUID().toString(),
                            "Jane Doe");
            return null;
        
    

【讨论】:

以上是关于如何在 Android Studio 的登录活动模板中实现 AsyncTask的主要内容,如果未能解决你的问题,请参考以下文章

在 android studio 中使用 SQLite 显示上次登录的用户数据

用户登录但未登录时的Android Studio按钮onclick重定向条件

如何在Android Studio的活动底部制作Android xml Linearlayout?

如何在所有活动中使用Android Studio默认导航抽屉[重复]

android.os.NetworkOnMainThreadException 试图从 android 活动访问数据库。 Android Studio [重复]

在Android studio中使用活动模板时,我无法添加新的Java类吗?