Score Tracker Implementation
This application demonstrates the implementation of a score tracking system for two teams using modern Android architecture components. The app features real-time score updates, undo functionality, and data persistence during configuration changes.
Gradle Configuration
Add the following configurations to your app-level build.gradle file:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
buildFeatures {
dataBinding true
}
}
ScoreViewModel Implementation
The ViewModel manages the score data and business logic:
package com.example.scoretracker;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class ScoreViewModel extends ViewModel {
private MutableLiveData<integer> teamAScore;
private MutableLiveData<integer> teamBScore;
private int previousTeamA, previousTeamB;
public MutableLiveData<integer> getTeamAScore() {
if (teamAScore == null) {
teamAScore = new MutableLiveData<>();
teamAScore.setValue(0);
}
return teamAScore;
}
public MutableLiveData<integer> getTeamBScore() {
if (teamBScore == null) {
teamBScore = new MutableLiveData<>();
teamBScore.setValue(0);
}
return teamBScore;
}
public void addToTeamA(int points) {
savePreviousScores();
teamAScore.setValue(teamAScore.getValue() + points);
}
public void addToTeamB(int points) {
savePreviousScores();
teamBScore.setValue(teamBScore.getValue() + points);
}
public void resetScores() {
savePreviousScores();
teamAScore.setValue(0);
teamBScore.setValue(0);
}
public void undoLastAction() {
teamAScore.setValue(previousTeamA);
teamBScore.setValue(previousTeamB);
}
private void savePreviousScores() {
previousTeamA = teamAScore.getValue();
previousTeamB = teamBScore.getValue();
}
}</integer></integer></integer></integer>
MainActivity Setup
The activity initializes data binding and connecst the ViewModel:
package com.example.scoretracker;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import com.example.scoretracker.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ScoreViewModel scoreViewModel;
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
scoreViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
binding.setViewModel(scoreViewModel);
binding.setLifecycleOwner(this);
}
}
Layout with Data Binding
The layout uses data binding expressions to connect UI elements with the ViewModel:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.example.scoretracker.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Team labels -->
<TextView
android:id="@+id/teamALabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Team A"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/teamBLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Team B"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Score displays -->
<TextView
android:id="@+id/teamAScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.teamAScore)}"
android:textSize="48sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/teamALabel" />
<TextView
android:id="@+id/teamBScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.teamBScore)}"
android:textSize="48sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/teamBLabel" />
<!-- Point buttons for Team A -->
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+1"
android:onClick="@{()->viewModel.addToTeamA(1)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/teamAScore" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+2"
android:onClick="@{()->viewModel.addToTeamA(2)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/previousButton" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+3"
android:onClick="@{()->viewModel.addToTeamA(3)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/previousButton2" />
<!-- Point buttons for Team B -->
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+1"
android:onClick="@{()->viewModel.addToTeamB(1)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/teamBScore" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+2"
android:onClick="@{()->viewModel.addToTeamB(2)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/previousButton3" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="+3"
android:onClick="@{()->viewModel.addToTeamB(3)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/previousButton4" />
<!-- Action buttons -->
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="Undo"
android:onClick="@{()->viewModel.undoLastAction()}"
app:srcCompat="@drawable/ic_undo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="Reset"
android:onClick="@{()->viewModel.resetScores()}"
app:srcCompat="@drawable/ic_reset"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The application provides a clean separation of concerns with the ViewModel handling data persistence during configuraton changes and the data binding simplifying the UI updates.