This project is written in Kotlin. Uses concepts of Clean Architecture and Offline First.
MVVM is the default Design Pattern.
Kotlin Coroutines for Asynchronous Tasks.
Minimum Android SDK: API 21
+-------------------------------------+
| |
| APP |
| |
+-------------------------------------+
| | |
| | |
v v v
+---------+ +---------+ +-----------+
| | | | | |
| AUTH | | HOME | | PROFILE |
| | | | | |
+---------+ +---------+ +-----------+
| | |
| | |
v v v
+-------------------------------------+
| |
| CORE |
| |
+-------------------------------------+
-
App Module
: Splash Screen, Main Screen and Navigation Graphs. Knows all feature modules. -
Auth Module
: Auth Feature. -
Home Module
: Home Feature. -
Profile Module
: Profile Feature. -
Core Module
: Navigator, Navigation IDs, Custom Views, Datasources, Repositories. All core logic.
-
Room
for Database Implementation -
Glide
for Image Processing -
Koin
for Dependency Injection -
OkHttp
for HTTP/HTTPS Requests -
Retrofit
for API Mapping Datasources -
Gson
for Json Parser -
Google Auth
for Social Login with Google -
Facebook Login
for Social Login with Facebook
Builds are made with Kotlin DSL and generated in this directory:
<ROOT_DIR>/builds/<MODULE_NAME>
Algorithm | Key Size | Cipher |
---|---|---|
RSA | 2048 | RSA/ECB/PKCS1Padding |
With the KeyStore
class it is possible to encrypt and decrypt bytes in a simple way, but we have a limitation.
The maximum number of bytes supported for encryption is calculated as follows:
floor(floor(KEY_SIZE.toDouble() / 8) - 11).toInt()
If we set KEY_SIZE = 2048
, we can only encrypt 248 bytes
or an exception will be thrown.
To work around this problem, we can "break" an array of bytes in equal sizes of 248 bytes
and encrypt part by part and concatenate the results.
For example. If we want to encrypt an array of bytes of size 485 bytes
, we will break into two parts of at most 248 bytes
:
248 + 237 = 485 bytes
Next, we will encrypt the first part (248 bytes
) which will generates an array of bytes with a total of 256 bytes
.
Finally, we will encrypt the second part (237 bytes
) which will generates an array of bytes with a total of 256 bytes
.
Concatenating the two parts will have an array of bytes with 512 bytes
.
To decrypt this array resulting from the concatenation of these two parts, do the following:
512 / 2 = 256 bytes
The first part is decrypted, and then the second part is decrypted. Then the first part is concatenated with the second part. At the end, we will have the original byte array.
To encrypt an array of bytes use:
AppKeyStore.encode(ByteArray): ByteArray
To dencrypt an array of bytes use:
AppKeyStore.decode(ByteArray): ByteArray
Essential dependency injections happen asynchronously during Splash Screen.
Each dependency injection happens gradually as the user navigates through the App.
If device is offline a message will be appear in the bottom of screen.
Oauth 2 for authentication.
Auto Refresh Token if necessary.
API responds with envelope:
{
"meta": {
"message": ""
},
"body": {}
}
Writing...
AuthException
for authentication errors.ConnectionException
for connection errors.ServerException
for server errors.UnauthorizedException
for unauthorized error.SyncException
for sync errors.
Throwable.toAppError(Context): AppError
Values are encrypted with the AppKeyStore
.
Between Activities:
Navigator.nav(Screen, Bundle)
Between Fragments:
Navigator.nav(NavController, Int, Bundle)
By default, views are included in the content
container of the base layout.
All activity base layouts are implemented by IActivityLayoutView
. The default layout follows:
<androidx.constraintlayout.widget.ConstraintLayout>
<!-- APP BAR -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
... />
<!-- CONTENT -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
... />
<!-- LOADING -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loading"
... />
<!-- MESSAGE -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/message"
... />
<!-- INFO -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/info"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
All fragment base layouts are implemented by ILayoutView
. The default layout follows:
<androidx.constraintlayout.widget.ConstraintLayout>
<!-- CONTENT -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fragment_content"
... />
<!-- LOADING -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fragment_loading"
... />
<!-- MESSAGE -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fragment_message"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
To create a layout for an activity:
<com.domain.skeleton.core.view.activity.ActivityView>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.domain.skeleton.core.view.activity.ActivityView>
To create a layout for a fragment:
<com.domain.skeleton.core.view.fragment.FragmentView>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.domain.skeleton.core.view.activity.ActivityView>
All default app views are injected to all activities and fragments and are access by appViews
attribute.
-
ToolbarView
is the default toolbar view. -
LoadingView
is the default loading view. -
UnauthorizedView
is the default unauthorized view. -
NoConnectionView
id the default no connection message view. -
CustomErrorView
is the default view for custom messages.
appViews.customError.message.text = "Message"
appViews.customError.setOnClickListener {
myFunction.invoke()
}
rootView.setMessage(appViews.customError)
https://console.developers.google.com > New Project
Dashboard > Enable APIs and Services > Search for "Google People" > Enable
Overview > Create Credentials
Get Fingerprint (SHA-1):
keytool -importkeystore \
-srckeystore ~/.android/debug.keystore \
-destkeystore ~/.android/debug.keystore \
-deststoretype pkcs12
keytool -keystore ~/.android/debug.keystore -list -v
Set Package Name:
com.domain.skeleton
https://developers.facebook.com/apps > Add New Project
Login do Facebook > Inicio Rapido > Android
Package Name:
com.domain.skeleton
Class:
com.domain.skeleton.auth.activity.LoginActivity
Debug:
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
Release:
keytool -exportcert -alias YOUR_RELEASE_KEY_ALIAS -keystore YOUR_RELEASE_KEY_PATH | openssl sha1 -binary | openssl base64
Paste Hash Key:
/core/res/values/system.xml
<string name="api_facebook_app_id">APP_ID</string>
<string name="api_facebook_login_protocol_scheme">LOGIN_PROTOCOL_SCHEME</string>
git clone https://github.com/junioregis/skeleton-rails.git
https://github.com/junioregis/skeleton-rails/wiki
Start the local server.