Here’s a detailed guide on implementing a login feature in an Android Expense Manager app using Retrofit for network requests. This guide covers creating the UI, setting up the necessary classes for handling API requests, and integrating them into the app.
Login Feature for Expense Manager Android App Using Retrofit
The login feature allows users to authenticate with the app, ensuring secure access to personalized data. This implementation includes creating a login screen, handling API requests with Retrofit, and managing user sessions.
1. Create the Login Screen Layout
Create the login screen layout (activity_login.xml
) with input fields for the username, password, and a button to initiate the login.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.login.LoginActivity"
android:background="@color/white_pressed"
android:padding="@dimen/padding_12">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/padding_12"
android:orientation="vertical"
android:layout_marginTop="@dimen/margin_48"
android:background="@drawable/login_card_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/welcome_back"
android:textSize="@dimen/font_size_xlarge"
style="@style/TextViewMediumFont"
android:textColor="@color/black_333333"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
android:layout_marginTop="@dimen/margin_16">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email"
android:textSize="@dimen/font_size_medium"
android:background="@drawable/edittext_bg"
style="@style/EditTextRegularFont"
android:inputType="textEmailAddress"
android:maxLines="1"/>
<requestFocus/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_12"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:textSize="@dimen/font_size_medium"
android:background="@drawable/edittext_bg"
style="@style/EditTextRegularFont"
android:inputType="textPassword"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_12"
android:text="@string/login"
android:textAllCaps="false"
android:textSize="@dimen/font_size_medium"
android:textColor="@color/white"
android:textAlignment="center"
android:background="@drawable/login_button_bg"
style="@style/TextViewMediumFont"/>
</LinearLayout>
</LinearLayout>
2. Define the LoginRequest and LoginResponse Models
Define the data models for the login request and response. These classes will handle the data sent to and received from the API.
Implement the LoginModel
class.
package in.infovistar.expensemanager.models;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class LoginModel {
@SerializedName("status")
@Expose
private Boolean status;
@SerializedName("message")
@Expose
private String message;
@SerializedName("result")
@Expose
private LoginResultModel result;
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public LoginResultModel getResult() {
return result;
}
public void setResult(LoginResultModel result) {
this.result = result;
}
}
LoginResultModel
class.package in.infovistar.expensemanager.models;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class LoginResultModel {
@SerializedName("id")
@Expose
private String id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("email")
@Expose
private String email;
@SerializedName("password")
@Expose
private String password;
@SerializedName("country_code")
@Expose
private String countryCode;
@SerializedName("mobile_number")
@Expose
private String mobileNumber;
@SerializedName("branch_id")
@Expose
private String branchId;
@SerializedName("image_url")
@Expose
private String imageUrl;
@SerializedName("role")
@Expose
private String role;
@SerializedName("access_group_id")
@Expose
private String accessGroupId;
@SerializedName("city")
@Expose
private String city;
@SerializedName("secret_token")
@Expose
private String secretToken;
@SerializedName("ip_address")
@Expose
private String ipAddress;
@SerializedName("created_at")
@Expose
private String createdAt;
@SerializedName("updated_at")
@Expose
private String updatedAt;
@SerializedName("status")
@Expose
private String status;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
public String getMobileNumber() {
return mobileNumber;
}
public void setMobileNumber(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
public String getBranchId() {
return branchId;
}
public void setBranchId(String branchId) {
this.branchId = branchId;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getAccessGroupId() {
return accessGroupId;
}
public void setAccessGroupId(String accessGroupId) {
this.accessGroupId = accessGroupId;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getSecretToken() {
return secretToken;
}
public void setSecretToken(String secretToken) {
this.secretToken = secretToken;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
3. Define the ApiService Interface
package in.infovistar.expensemanager.api;
import java.util.HashMap;
import in.infovistar.expensemanager.models.CustomerModel;
import in.infovistar.expensemanager.models.LoginModel;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
public interface ApiService {
@FormUrlEncoded
@POST("login")
Call<LoginModel> getLogin(@FieldMap HashMap<String, Object> hashMap);
}
4. Create the LoginActivity
Implement the LoginActivity
class, which will handle the login process, user input, and API interaction.
package in.infovistar.expensemanager.activities.login;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.textfield.TextInputLayout;
import java.util.HashMap;
import in.infovistar.expensemanager.ExpenseManagerApp;
import in.infovistar.expensemanager.R;
import in.infovistar.expensemanager.activities.MainActivity;
import in.infovistar.expensemanager.api.ApiService;
import in.infovistar.expensemanager.models.LoginModel;
import in.infovistar.expensemanager.session.Preferences;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class LoginActivity extends AppCompatActivity {
private ExpenseManagerApp expenseManagerApp;
private Context context;
private Preferences preferences;
private ApiService apiService;
private TextInputLayout tilEmail;
private TextInputLayout tilPassword;
private TextView btnLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
init();
btnLogin.setOnClickListener(v -> {
validate();
});
}
private void validate() {
if(tilEmail.getEditText().getText().toString().trim().isEmpty()) {
tilEmail.setError(getString(R.string.please_enter_email_address));
} else {
tilEmail.setErrorEnabled(false);
if(tilPassword.getEditText().getText().toString().trim().isEmpty()) {
tilPassword.setError(getString(R.string.please_enter_password));
} else {
tilPassword.setErrorEnabled(false);
submit();
}
}
}
private void submit() {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("email", tilEmail.getEditText().getText().toString());
hashMap.put("password", tilPassword.getEditText().getText().toString());
apiService
.getLogin(hashMap)
.enqueue(new Callback<LoginModel>() {
@Override
public void onResponse(Call<LoginModel> call, Response<LoginModel> response) {
if(response.isSuccessful()) {
Toast.makeText(context, response.body().getMessage(), Toast.LENGTH_SHORT).show();
if(response.body().getStatus()) {
preferences.setLogin(response.body().getResult());
startActivity(new Intent(context, MainActivity.class));
finish();
}
}
}
@Override
public void onFailure(Call<LoginModel> call, Throwable t) {
Toast.makeText(expenseManagerApp, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void init() {
context = this;
expenseManagerApp = (ExpenseManagerApp) getApplicationContext();
preferences = expenseManagerApp.getPreferences();
apiService = expenseManagerApp.getApiService();
tilEmail = findViewById(R.id.til_email);
tilPassword = findViewById(R.id.til_password);
btnLogin = findViewById(R.id.btn_login);
}
}
5. Session Management
Use the Session
class defined earlier to manage user login status and store the user token for authenticated requests.
6. API Endpoint Example
Ensure that the API endpoint (/users/login
) is correctly implemented on your server side. The server should validate the credentials and return a response with a success status, a message, and a token if authentication is successful.
8. Testing and Integration
- Testing: Run the app on an emulator or physical device. Enter valid and invalid credentials to test the response.
- Error Handling: Implement robust error handling to manage network errors, validation errors, and unexpected issues.
Let me know if you need further assistance or if you need to make any adjustments to the code!