- Project Structure
- Convention
- RxJava
- Common Scripts
- Technical Stack
- Dependencies
- Library
- Architecture
- Case Study
- Authors
- Pull newest code from develop branch
- Create new branch from develop branch
- Code, test on new branch. Make sure that all feature is true and have no error
- Pull newest code from develop branch again, merge into new branch
- Create merge request
- feat (new feature for the user, not a new feature for build script)
- fix (bug fix for the user, not a fix to a build script)
- docs (changes to the documentation)
- style (formatting, missing semi colons, etc; no production code change)
- refactor (refactoring production code, eg. renaming a variable)
- test (adding missing tests, refactoring tests; no production code change)
- chore (updating grunt tasks etc; no production code change)
- Branch name: feature/{feature_name}
- Commit message describe core of commit change. Start with:
- feature: description
- fix: description
- refactor: Refactoring source code, optimize source code...
RxJava
- Single: is an asynchronously retrieved: one-shot value
- Observable: class that emits a stream of data or events. i.e. a class that can be used to perform some action, and publish the result: streams of data
- Flowable: streams of data
- Observer: class that receivers the events or data and acts upon it. i.e. a class that waits and watches the Observable, and reacts whenever the Observable publishes results.
- Publish Subject: It emits all the subsequent items of the source Observable at the time of subscription.
- Replay Subject: It emits all the items of the source Observable, regardless of when the subscriber subscribes.
- Behavior Subject: It emits the most recently emitted item and all the subsequent items of the source Observable when an observer subscribes to it.
- Async Subject: It only emits the last value of the source Observable(and only the last value) only after that source Observable completes.
Scripts
Clear build folder
find . -name build -exec rm -rf {} \;
find . -name build -exec rm -r "{}" \;
Gradle
task clean(type: Delete) {
delete rootProject.buildDir
}
Remove all DS_Store
find . -name '.DS_Store' -type f -delete
find . -name ".DS_Store" -type d -exec rm -r "{}" \;
find . -name ".hprof" -type d -exec rm -r "{}" \;
Remove all iml
find . -name '.iml' -type f -delete
Clear cache
git rm -r --cached .idea | git rm -r --cached *.iml
Dependencies
./gradlew -q dependencies [module]:dependencies
Pull with rebase default
git config --global pull.rebase true
-
β οΈ -- In ViewBinding, every XML layout file in a module gets a corresponding generated binding class, whose name is in Pascal Case format, with the word Binding appended at the end and implements ViewBinding interface.
-
β οΈ -- Files are generated into Java not Kotlin, reason for that would be discussed later, but it has nothing todo with interops between Java-Kotlin
-
β οΈ -- Generated for : tag needs to have an id: android:id="@+id/includes". This is required for View binding to generate the binding class like normal layout.
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<!-- include layout example -->
<include
android:id="@+id/includes"
layout="@layout/included_layout" />
</FrameLayout>
// file name : included_layout.xml
<LinearLayout ... >
<TextView android:id="@+id/text_demo1" ... />
</LinearLayout>
- β οΈ -- Generated for : tag, the system ignores the element and its child views directly added into the layout, in place of the tag.
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<!-- include layout example -->
<include
android:id="@+id/merges" [β wrong!]
layout="@layout/merged_layout" />
<!-- merge layout example -->
<include layout="@layout/merged_layout" /> [β
]
</FrameLayout>
// file name : merged_layout.xml
<merge ... >
<TextView android:id="@+id/text_demo2" ... />
</merge>
Even if you try to provide an ID to the included layout with merge tag, it will generate the rootView inside the binding class, but on runtime it will crash the app, with missing id exception, this behaviour is obvious as findViewById cannot get the root view of the merged layout as it has been flatten out by the system to be included inside the parent layout.
- β οΈ -- Ignore view from generation
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
β Create new Activity (with/without Dagger) use ViewBinding: ViewBindingActivity / DaggerViewBindingActivity
// Without Dagger
class ExampleActivity : ViewBindingActivity<ActivityExampleBinding>()
// With Dagger
class ExampleActivity : DaggerViewBindingActivity<ActivityExampleBinding>()
β Create new Activity (with/without Dagger) use DataBinding: DataBindingActivity / DaggerDataBindingActivity
// Without Dagger
class ExampleActivity : DataBindingActivity<ActivityExampleBinding>(R.layout.activity_example)
// With Dagger
class ExampleActivity : DaggerDataBindingActivity<ActivityExampleBinding>(R.layout.activity_example)
β Create new Fragment (with/without Dagger) use ViewBinding: ViewBindingFragment / DaggerViewBindingFragment
// Without Dagger
class ExampleFragment : ViewBindingFragment<FragmentExampleBinding>
// With Dagger
class ExampleFragment : DaggerViewBindingFragment<FragmentExampleBinding>
β Create new Fragment (with/without Dagger) use DataBinding: DataBindingFragment / DaggerDataBindingFragment
// Without Dagger
class ExampleFragment : DataBindingFragment<FragmentExampleBinding>(R.layout.fragment_example)
// With Dagger
class ExampleFragment : DaggerDataBindingFragment<FragmentExampleBinding>(R.layout.fragment_example)
β Create new BottomSheetDialogFragment (with/without Dagger) use ViewBinding: ViewBindingBSDF
class ExampleDialogBSDF : ViewBindingBSDF<DialogExampleBinding>() {
override val bindingInflater: Transformer<LayoutInflater, DialogExampleBinding>
get() = DialogExampleBinding::inflate
}
β Create new BottomSheetDialogFragment (with/without Dagger) use DataBinding: DataBindingBSDF
class ExampleDialogBSDF : DataBindingBSDF<DialogExampleBinding>(R.layout.fragment_example) {
}
β Create new DialogFragment (with/without Dagger) use ViewBinding: ViewBindingDF
class ExampleDialogBSDF : ViewBindingDialogFragment<DialogExampleBinding>() {
override val bindingInflater: Transformer<LayoutInflater, DialogExampleBinding>
get() = DialogExampleBinding::inflate
}
This project takes advantage of many popular libraries, plugins and tools of the Android ecosystem. Most of the libraries are in the stable version, unless there is a good reason to use non-stable dependency.
The architecture is built around Android Architecture Components.
We followed the recommendations laid out in the Guide to App Architecture when deciding on the architecture for the app. We kept logic away from Activities and Fragments and moved it to ViewModels. We observed data using LiveData and used the Data Binding Library to bind UI components in layouts to the app's data sources.
We used a Repository layer for handling data operations. FUTA's data comes from a few different sources - user data is stored in Cloud Firestore (either remotely or in a local cache for offline use), user preferences and settings are stored in SharedPreferences, conference data is stored remotely and is fetched and stored in memory for the app to use, etc. - and the repository modules are responsible for handling all data operations and abstracting the data sources from the rest of the app (we liked using Firestore, but if we wanted to swap it out for a different data source in the future, our architecture allows us to do so in a clean way).
We made an early decision to rewrite the app from scratch to bring it in line with our thinking about modern Android architecture. Using Kotlin for the rewrite was an easy choice: we liked Kotlin's expressive, concise, and powerful syntax; we found that Kotlin's support for safety features for nullability and immutability made our code more resilient; and we leveraged the enhanced functionality provided by Android Ktx extensions.
- Jetpack:
- Android KTX - provide concise, idiomatic Kotlin to Jetpack and Android platform APIs.
- AndroidX - major improvement to the original Android Support Library, which is no longer maintained.
- Data Binding - allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
- View Binding - View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout.
- Lifecycle - perform actions in response to a change in the lifecycle status of another component, such as activities and fragments.
- LiveData - lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.
- Navigation - helps you implement navigation, from simple button clicks to more complex patterns, such as app bars and the navigation drawer.
- Paging - helps you load and display small chunks of data at a time. Loading partial data on demand reduces usage of network bandwidth and system resources.
- ViewModel - designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
- Worker - A class that performs work synchronously on a background thread provided by WorkManager.
- Dagger2 - dependency injector for replacement all FactoryFactory classes.
- Retrofit - type-safe HTTP client.
- Timber - a logger with a small, extensible API which provides utility on top of Android's normal Log class.
- Stetho - debug bridge for applications via Chrome Developer Tools.
- Shimmer - Shimmer is an Android library that provides an easy way to add a shimmer effect to any view in your Android app.
- Glide - Glide is a fast and efficient open source media management and image loading framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface.