[go: up one dir, main page]

0% found this document useful (0 votes)
15 views39 pages

CH1 - Android Architecture Components

The document outlines the architecture of Android applications, emphasizing the importance of component declarations, user behavior, and data management. It discusses architectural principles such as Separation of Concerns, Single Source of Truth, and Unidirectional Data Flow, which enhance scalability and maintainability. Additionally, it presents a recommended app architecture with UI, Data, and optional Domain layers, detailing the roles of repositories and use cases in managing data effectively.

Uploaded by

nurlita989
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views39 pages

CH1 - Android Architecture Components

The document outlines the architecture of Android applications, emphasizing the importance of component declarations, user behavior, and data management. It discusses architectural principles such as Separation of Concerns, Single Source of Truth, and Unidirectional Data Flow, which enhance scalability and maintainability. Additionally, it presents a recommended app architecture with UI, Data, and optional Domain layers, detailing the roles of repositories and use cases in managing data effectively.

Uploaded by

nurlita989
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 39

Android Architecture

Components
Component Declarations, User
Behavior, & Data Management
• Component Declaration:Android apps consist of multiple components
(Activities, Fragments, Services, etc.) declared in the Android Manifest.
• User Behavior:Users frequently switch between apps quickly, leading to
highly dynamic interaction flows.
• Resource Constraints:Due to mobile devices’ limited resources, the OS
may kill app processes at any time to free up memory for other apps.
• Risk of Storing Data in Components:Components can be launched
individually and out-of-order, so storing data or state directly in
components (such as an Activity) risks losing that data when the
component is destroyed.
• Recommendation:Use a separate mechanism that remains unaffected
by the UI component lifecycle to ensure data consistency and resilience.
Scenario Example
public class MainActivity extends AppCompatActivity {
// Using EditText so the user can manually change the •Scenario:
text 1.The Activity is created and fetchUserData()
private EditText nameEditText; sets the text to "Jane Doe".
private User currentUser; 2.The user manually changes the text in the
EditText to "Jane A. Doe".
@Override 3.On screen rotation, the Activity is destroyed
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
and recreated.
setContentView(R.layout.activity_main); 4.In onCreate, fetchUserData() is called again
nameEditText = findViewById(R.id.nameEditText); and resets the text to "Jane Doe", causing the
user's change to be lost.
// Fetch data from a source (e.g., server or database) •Key Issue:
// and always set the text to the default value •User-modified data is not preserved because
fetchUserData(); the data fetching logic in the Activity always
}
resets the default value.
•The user experience is disrupted as their
private void fetchUserData() {
// Simulate data fetching that always returns the changes are overwritten upon configuration
default data changes.
currentUser = new User("Jane Doe",
"jane.doe@example.com");
nameEditText.setText(currentUser.getName());
Common Architectural Principles &
Why They Matter
• Why Are Architectural Principles Important?As
Android apps grow in size, a well-defined architecture is
crucial for scalability, robustness, and ease of testing.
• An app architecture sets the boundaries between parts
of the app and assigns clear responsibilities.
• By establishing these boundaries, you reduce
dependencies and make the app more maintainable and
adaptable.
Common Architectural Principles
• Separation of Concerns
• Drive UI from data models
• Single Source of Truth
• Unidirectional Data Flow
1. Separation of Concerns
• Key Principle: Separation of ConcernsSeparate UI from Business
Logic: Avoid writing all your code within an Activity or Fragment.
• Role of Activities/Fragments:
• They should only manage UI interactions and communicate with the operating
system.
• They act as "glue" between the OS and your app, not as containers for business
logic.
• Benefits:
• Reduces issues related to component lifecycle.
• Makes unit testing easier since business logic resides in separate, testable layers.
• Keep in Mind: You don't control the implementations of Activity and
Fragment; the OS can destroy them anytime (e.g., due to low memory
conditions).
2. Drive UI from data models
•Data Model as the Driver for UI:
•The UI should be driven by data models, preferably persistent ones.
•Data models represent the app's data independently from UI elements and
other components.
•Although data models are not tied to the UI/component lifecycle, they are still
subject to removal when the OS kills the app process.

•Advantages of Persistent Models:


•Users don't lose data if the OS terminates the app to free up resources.
•The app remains functional even when network connectivity is unreliable or
unavailable.
•If you base your app architecture on data model classes, you make your app
more testable and robust.
3. Single Source of Truth SSOT
•Single Source of Truth (SSOT) Principle:
•For every new data type defined in your app, assign a single source
of truth as the owner of that data.
•The SSOT exposes the data as immutable and is the only component
that can modify it.

•Benefits of SSOT:
•Centralizes all modifications of a particular data type in one place.
•Protects the data from being tampered with by other components.
•Makes data changes more traceable, facilitating bug detection.
•In offline-first applications, the source of truth is typically a local
database; in other cases, it can be a ViewModel or even the UI.
SSOT Example
// UserRepository acts as the Single Source of Truth (SSOT) for Explanation:
user data. 1.Protecting Data from Unauthorized Changes:
class UserRepository(private val userDao: UserDao) { In this example, the UserRepository is the only component that can
// User data is stored in a private MutableLiveData. modify user data, since it encapsulates a private MutableLiveData.
private val _userData = MutableLiveData<User>() This prevents other parts of the app from altering the data directly.
// Data is exposed as immutable LiveData to observers. 2.Making Data Changes Traceable:
val userData: LiveData<User> Since all modifications are centralized in functions like
get() = _userData updateUser(), tracking changes becomes easier. This
centralized control simplifies debugging, as any change to
// Function to fetch user data from the database and update the data must pass through the repository.
the SSOT. 3.Offline-First and Alternative Sources of Truth:
fun fetchUserData(userId: Int) { Here, the database (accessed via userDao) serves as the
// For example, retrieving user data from the database. primary source of truth. However, in different scenarios, the
val user = userDao.getUserById(userId) source of truth could be a ViewModel or even the UI,
_userData.value = user depending on the application architecture.
}
The example above illustrates how SSOT can be implemented to
// Function to update user data. maintain data integrity and consistency in mobile applications. By
// Only this repository can modify the data, ensuring using this pattern, you can ensure that data is always maintained
consistency. and changes can be easily tracked. The repository will be discussed
fun updateUser(user: User) { further in the next section
// Update data in the database.
userDao.updateUser(user)
// Update the SSOT.
_userData.value = user
}
4. Unidirectional Data Flow
•Basic Concept:
•In Unidirectional Data Flow (UDF), Data (state) flows in only one direction, while events that modify the
data flow in the opposite direction.
•Data Flow in Android:
•Top-Down Flow:
•Data flows from higher-scoped components (e.g., data sources or the SSOT) down to lower-scoped
components (e.g., the UI).
•Bottom-Up Flow:
•User events (such as button clicks) flow upward from the UI to the SSOT, where the application data is
modified and then exposed as immutable.
•Benefits of UDF:
•Better guarantees data consistency.
•Less prone to errors due to a clear data flow.
•Easier to debug.
•Leverages all the benefits of the Single Source of Truth (SSOT) pattern.
Recommended app architecture
•Each application should have at least two layers:
•UI Layer: Displays application data on the screen.
•Data Layer: Contains the business logic of your app and exposes application data.
•An additional layer called the Domain Layer can be added to simplify and reuse interactions between
the UI and Data layers.
Recommended app architecture

•Role of the UI Layer:


•Displays application data on the screen.
•Whenever the data changes—due to user interactions (such as
button presses) or external input (like a network response)—the UI
should update accordingly.
•Key Components:
1.UI Elements:
•Render the data on the screen using Views or Jetpack Compose
functions.
2.State Holders:
•Classes such as ViewModel that hold data, expose it to the UI,
and handle application logic.
Example - We will discuss
Viewmodels in the next section
<?xml version="1.0" encoding="utf-8"?> public class CounterViewModel extends ViewModel {
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/andr // Holds the counter state as LiveData
oid" private final MutableLiveData<Integer> counter = new
android:layout_width="match_parent" MutableLiveData<>();
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"> public CounterViewModel() {
counter.setValue(0); // Initialize with 0
<!-- UI element to display data -->
<TextView }
android:id="@+id/counterText"
android:layout_width="wrap_content" public LiveData<Integer> getCounter() {
android:layout_height="wrap_content"
android:text="0" return counter;
android:textSize="32sp" }
android:layout_gravity="center_horizontal" />

<!-- UI element to receive user input --> // Handles the logic to increment the counter
<Button public void onIncrement() {
android:id="@+id/incrementButton" int current = counter.getValue() != null ?
android:layout_width="wrap_content"
android:layout_height="wrap_content" counter.getValue() : 0;
android:text="Increment" counter.setValue(current + 1);
android:layout_gravity="center_horizontal" }
android:layout_marginTop="16dp" />
</LinearLayout> }
Example - We will discuss
Viewmodels in the next section
public class MainActivity extends AppCompatActivity {
private TextView counterText;
Explanation (English):
private Button incrementButton; •UI Elements:
private CounterViewModel viewModel;
The TextView and Button are
@Override
protected void onCreate(Bundle savedInstanceState) {
defined in the XML layout and
super.onCreate(savedInstanceState); rendered by the Activity.
setContentView(R.layout.activity_main);
•State Holder:
// Initialize UI elements
counterText = findViewById(R.id.counterText);
The CounterViewModel holds
incrementButton = findViewById(R.id.incrementButton); the counter state and handles
// Initialize ViewModel as the state holder the logic for incrementing it.
viewModel = new ViewModelProvider(this).get(CounterViewModel.class); •Interaction:
// The UI observes data from the ViewModel When the button is pressed,
viewModel.getCounter().observe(this, count -> {
counterText.setText(String.valueOf(count));
an event is sent to the
}); ViewModel to update the
// Send event to the ViewModel when the button is clicked state, and the UI
incrementButton.setOnClickListener(v -> {
viewModel.onIncrement();
automatically reflects the
}); new value through LiveData
}
} observation.
Recommended app architecture
Data Layer:
• Contains the business logic that defines how your
app creates, stores, and modifies data.
• This business logic gives value to your app by
enforcing the rules that manage the data.
Repository:
• Created for each type of data (e.g.,
MoviesRepository for movie data,
PaymentsRepository for payment data).
• Responsible for:
• Exposing data to the rest of the app.
• Centralizing data changes.
• Resolving conflicts between multiple data
sources.
• Abstracting data sources from the rest of the
app.
• Containing the business logic related to the
data.
Data Source:
• A class responsible for interacting with a single
source of data (e.g., file, network source, or local
database).
• Acts as the bridge between the application and the
Example - We will discuss Repositories in the next section
public class MoviesRepository {
private final MoviesRemoteDataSource remoteDataSource;
private final MoviesLocalDataSource localDataSource;

public MoviesRepository(MoviesRemoteDataSource remote,


MoviesLocalDataSource local) {
this.remoteDataSource = remote;
this.localDataSource = local;
}

public LiveData<List<Movie>> getMovies() {


// First, attempt to retrieve data from the local source
LiveData<List<Movie>> localMovies = localDataSource.getMovies();
if (localMovies.getValue() == null || localMovies.getValue().isEmpty()) {
// If the local data is empty, fetch from the remote source and save locally
List<Movie> moviesFromRemote = remoteDataSource.fetchMovies();
localDataSource.saveMovies(moviesFromRemote);
return localDataSource.getMovies();
}
return localMovies;
}
}
Example - We will discuss Repositories in the next section

•The MoviesRepository manages movie data by integrating two


data sources: a remote source (e.g., an API) and a local source (e.g.,
a local database).
•This repository exposes data through the getMovies() method,
which first attempts to retrieve data from the local source.
•If the local data is empty or unavailable, the repository fetches data
from the remote source, saves it locally, and then returns the updated
local data.
•This design centralizes all changes and business logic related to
movie data within the repository.
Recommended app architecture
English:
•Definition:
•The domain layer is an optional layer that sits between the UI
and data layers.
•Responsibilities:
•Encapsulates complex business logic or simple business logic
reused by multiple ViewModels.
•Manages complexity and enhances code reusability.
•Usage:
•Should be implemented only when needed—for instance, to
handle complexity or favor reusability.
•Classes in the Domain Layer:
•Commonly referred to as use cases or interactors.
•Each use case should be responsible for a single functionality.
•Example: a GetTimeZoneUseCase if multiple ViewModels
rely on time zones to display the proper message.
example
// Use case class responsible for retrieving time zone information
public class GetTimeZoneUseCase {
private final TimeZoneRepository timeZoneRepository;

// Repository is injected into the use case


public GetTimeZoneUseCase(TimeZoneRepository timeZoneRepository) {
this.timeZoneRepository = timeZoneRepository;
}

// Method that executes business logic to retrieve the time zone


public String execute() {
// Retrieve the time zone from the repository and format the message
(simple example)
String timeZone = timeZoneRepository.getTimeZone();
return "Current Time Zone: " + timeZone;
}
}
example
// Interface for the time zone repository
public interface TimeZoneRepository {
String getTimeZone();
}

// A simple implementation of the time zone repository


public class TimeZoneRepositoryImpl implements
TimeZoneRepository {
@Override
public String getTimeZone() {
// Return the system's default time zone (simple example)
return java.util.TimeZone.getDefault().getID();
}
}
example
public class TimeZoneViewModel extends ViewModel {
private final MutableLiveData<String> timeZoneLiveData = new
MutableLiveData<>();
private final GetTimeZoneUseCase getTimeZoneUseCase;

public TimeZoneViewModel() {
// Here, the repository is instantiated directly for the example,
// but in a real app, you should use dependency injection.
TimeZoneRepository repository = new TimeZoneRepositoryImpl();
getTimeZoneUseCase = new GetTimeZoneUseCase(repository);
loadTimeZone();
}

private void loadTimeZone() {


// Execute the use case to retrieve the time zone and update LiveData
String timeZone = getTimeZoneUseCase.execute();
timeZoneLiveData.setValue(timeZone);
}

public LiveData<String> getTimeZone() {


return timeZoneLiveData;
}
}
example
•Domain Layer (GetTimeZoneUseCase):
Encapsulates the business logic for retrieving and formatting time
zone information.
•Data Layer (TimeZoneRepository):
Responsible for accessing the data source (e.g., system's default
time zone).
•Usage in ViewModel (TimeZoneViewModel):
Utilizes the use case to fetch data and expose it via LiveData so that
the UI can observe and display the most up-to-date time zone
information.
Android Architecture Component
• A set of libraries that help
you build robust, testable,
and maintainable Android
apps.
• Overview:
The diagram on the side
illustrates how components
such as Activity/Fragment,
ViewModel, Repository, Room
(local database), and Rest
Client (network) interact with
each other..
Android Architecture Component
Some components of Android architecture that will be discussed
1. View (Activity/Fragment)
1. Acts as the UI controller.
2. Displays data and handles user interactions on the screen.
2. ViewModel
1. Supplies data to the UI and acts as the communication center between the Repository
and the UI.
2. Preserves data during configuration changes.
3. LiveData
1. A data holder class that can be observed.
2. Always holds the latest version of data and notifies its observers when the data
changes.
4. Repository
• Manages multiple data sources (network, local database, cache).
• Provides data to the ViewModel, deciding whether to fetch from Room or a Rest Client.
View Model dan
Lifecycle
Managing the UI Controller Lifecycle
(Activity/Fragment)
Role of the UI Controller:
• Displays data on the UI.
• Handles user interactions (events).
• Manages system communication (e.g., permissions).
• Can persist data using onSaveInstanceState() and onCreate() with a
Bundle.
Challenges:
• If the data to be preserved is large (such as a user list or Bitmaps), storing it in
a Bundle is not efficient.
• Asynchronous calls often run in the UI controller, increasing load and potential
memory leaks.
• Testing becomes more difficult when the UI controller handles too much logic.
Why Separate Data Sources from UI
Controller Logic?
• Issues with a Controller-Centric Approach:
• Storing large amounts of data can lead to performance problems.
• Asynchronous operations can cause memory leaks if not
managed properly.
• Complex logic within the UI controller makes testing more difficult.
• Solution:
• Separate business logic and data from the UI controller.
• Utilize components like ViewModel to handle data and its
lifecycle independently.
• Keep the UI controller “lightweight,” focusing on rendering and
user interactions.
ViewModel Implementation
•Definition:
•ViewModel is a class provided by Architecture Components to help UI controllers
(Activity/Fragment) prepare data for display.
•Key Advantage: A ViewModel survives configuration changes (e.g., screen
rotations), so data remains available.
•Main Benefits:
1.Persistent Data: Data stored in the ViewModel is not lost during screen rotation or
other configuration changes.
2.Separation of Concerns: Data management is handled by the ViewModel,
keeping the UI controller simpler.
•Use Case Example:
•Displaying user data (user profile).
•If you store user data in the ViewModel, a new Activity/Fragment can immediately
access that data after a rotation.
Example - Displaying User Data
1. UserViewModel stores the user’s name in LiveData.
2. MainActivity observes the LiveData.
3. When the user presses the “Change Name” button,
the ViewModel is updated, and the UI (TextView)
immediately changes to display the new name.

With this structure, the data is maintained even when


configuration changes (such as screen rotation)
occur, and the data logic is separated from the UI, making
the code easier to maintain and test.
Example – View (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">

<!-- TextView untuk menampilkan nama pengguna -->


<TextView
android:id="@+id/userNameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Default Name"
android:textSize="20sp" />

<!-- Button untuk mengubah nama pengguna -->


<Button
android:id="@+id/changeNameButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Name"
android:layout_marginTop="16dp" />
</LinearLayout>
ViewModel Example - java
public class UserViewModel extends ViewModel {
// LiveData to store the user's name
private final MutableLiveData<String> userName = new
MutableLiveData<>();

public UserViewModel() {
// Optional initial value
userName.setValue("Default Name");
}

// Method to retrieve the LiveData for the user's name


public LiveData<String> getUserName() {
return userName;
}

// Method to change the user's name


public void updateUserName(String newName) {
userName.setValue(newName);
}
*live data will be explained later
}
public class MainActivity extends AppCompatActivity {
private UserViewModel userViewModel;
Main Activity - Observe private TextView userNameTextView;
private Button changeNameButton;

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

// Menghubungkan view dengan ID


userNameTextView = findViewById(R.id.userNameTextView);
changeNameButton = findViewById(R.id.changeNameButton);

// Inisialisasi ViewModel
userViewModel = new
ViewModelProvider(this).get(UserViewModel.class);

// Observasi LiveData userName


userViewModel.getUserName().observe(this, newName -> {
// Perbarui teks pada TextView *live data will be explained
userNameTextView.setText(newName);
});

// Aksi klik untuk mengubah nama


changeNameButton.setOnClickListener(v -> {
// Misalnya, memperbarui nama secara acak
userViewModel.updateUserName("New Name " + new
Random().nextInt(100));
});
}
ViewModel Example - Kotlin
class UserViewModel : ViewModel() {
// LiveData to store the user's name
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> get() = _userName

init {
// Optional initial value
_userName.value = "Default Name"
}

// Method to change the user's name


fun updateUserName(name: String) {
_userName.value = name
}
} *live data will be explained later
class MainActivity : AppCompatActivity() {

Main Activity private lateinit var userViewModel: UserViewModel


private lateinit var userNameTextView: TextView
(Kotlin) - Observe private lateinit var changeNameButton: Button

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Link views by ID
userNameTextView = findViewById(R.id.userNameTextView)
changeNameButton = findViewById(R.id.changeNameButton)

// Initialize ViewModel
userViewModel = ViewModelProvider(this)[UserViewModel::class.java]

// Observe userName LiveData


userViewModel.userName.observe(this) { newName ->
// Update the TextView text
userNameTextView.text = newName
} *live data will be explained
// Click action to change the name
changeNameButton.setOnClickListener {
// For example, update the name randomly
val randomNumber = (1..100).random()
userViewModel.updateUserName("New Name $randomNumber")
}
}
}
Rotate your android device in the previous application
example. Is the user data back to default?
ViewModel Lifecycle within an
Activity
Reloading the Activity:
• When the Activity is reloaded (e.g., due to screen rotation), the
Android system requests the same ViewModel instance as was used
when the Activity was first created.
• This allows data in the ViewModel to persist and remain intact during
configuration changes.

onCleared() Call:
• When the Activity is truly finishing (for instance, the user presses the
Back button and the Activity is permanently destroyed), the UI
framework calls onCleared() on the ViewModel.
• The onCleared() method is the ideal place to clean up
resources, such as canceling coroutines or disposing of active
Disposables, preventing memory leaks.
ViewModel Lifecycle within an
Activity
Comparison of ViewModel vs.
savedInstanceState
Difference ViewModel saveInstanceState
Storage Location Memory Serialized to disk
Survives Yes Yes
Configuration Change
Data Limitation Complex objects are Primarily primitive
fine, limited by types and
available memory small/simple objects
(String, Integer, etc.)
Read/Write Speed Fast (memory access) Slow (requires
serialization/deseriali
zation and disk
access)
https://developer.android.com/topic/libraries/architecture/viewmodel

You might also like