Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,9 @@ Here are some useful Gradle/adb commands for executing this example:
## Discussions
Refer to the issues section: https://github.com/android10/Android-CleanArchitecture-Kotlin/issues

## Code style
Here you can download and install the java codestyle.
https://github.com/android10/java-code-styles

## License

Copyright 2018 Fernando Cejas
Copyright 2021 Fernando Cejas

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,27 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
class ApplicationModule {

@Provides @Singleton fun provideRetrofit(): Retrofit {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://raw.githubusercontent.com/android10/Sample-Data/master/Android-CleanArchitecture-Kotlin/")
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
.baseUrl("https://raw.githubusercontent.com/android10/Sample-Data/master/Android-CleanArchitecture-Kotlin/")
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}

private fun createClient(): OkHttpClient {
val okHttpClientBuilder: OkHttpClient.Builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
okHttpClientBuilder.addInterceptor(loggingInterceptor)
}
return okHttpClientBuilder.build()
}

@Provides @Singleton fun provideMoviesRepository(dataSource: MoviesRepository.Network): MoviesRepository = dataSource
@Provides
@Singleton
fun provideMoviesRepository(dataSource: MoviesRepository.Network): MoviesRepository = dataSource
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ sealed class Failure {
object ServerError : Failure()

/** * Extend this class for feature specific failures.*/
abstract class FeatureFailure: Failure()
abstract class FeatureFailure : Failure()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ package com.fernandocejas.sample.core.extension
import android.content.Context
import android.net.ConnectivityManager

val Context.connectivityManager: ConnectivityManager get() =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val Context.connectivityManager: ConnectivityManager
get() =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import com.fernandocejas.sample.core.platform.BaseFragment
import kotlinx.android.synthetic.main.activity_layout.*

inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) =
beginTransaction().func().commit()
beginTransaction().func().commit()

fun BaseFragment.close() = fragmentManager?.popBackStack()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import androidx.lifecycle.Observer
import com.fernandocejas.sample.core.exception.Failure

fun <T : Any, L : LiveData<T>> LifecycleOwner.observe(liveData: L, body: (T?) -> Unit) =
liveData.observe(this, Observer(body))
liveData.observe(this, Observer(body))

fun <L : LiveData<Failure>> LifecycleOwner.failure(liveData: L, body: (Failure?) -> Unit) =
liveData.observe(this, Observer(body))
liveData.observe(this, Observer(body))
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ fun View.cancelTransition() {

fun View.isVisible() = this.visibility == View.VISIBLE

fun View.visible() { this.visibility = View.VISIBLE }
fun View.visible() {
this.visibility = View.VISIBLE
}

fun View.invisible() { this.visibility = View.GONE }
fun View.invisible() {
this.visibility = View.GONE
}

fun ViewGroup.inflate(@LayoutRes layoutRes: Int): View =
LayoutInflater.from(context).inflate(layoutRes, this, false)
LayoutInflater.from(context).inflate(layoutRes, this, false)

fun ImageView.loadFromUrl(url: String) =
Glide.with(this.context.applicationContext)
Expand All @@ -53,7 +57,8 @@ fun ImageView.loadUrlAndPostponeEnterTransition(url: String, activity: FragmentA
Glide.with(context.applicationContext).load(url).into(target)
}

private class ImageViewBaseTarget (var imageView: ImageView?, var activity: FragmentActivity?) : BaseTarget<Drawable>() {
private class ImageViewBaseTarget(var imageView: ImageView?, var activity: FragmentActivity?) :
BaseTarget<Drawable>() {
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
activity?.supportStartPostponedEnterTransition()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ sealed class Either<out L, out R> {
*/
fun <L> left(a: L) = Either.Left(a)


/**
* Creates a Left type.
* @see Right
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ abstract class UseCase<out Type, in Params> where Type : Any {

abstract suspend fun run(params: Params): Either<Failure, Type>

operator fun invoke(params: Params, scope: CoroutineScope = GlobalScope, onResult: (Either<Failure, Type>) -> Unit = {}) {
operator fun invoke(
params: Params,
scope: CoroutineScope = GlobalScope,
onResult: (Either<Failure, Type>) -> Unit = {}
) {
scope.launch(Dispatchers.Main) {
val deferred = async(Dispatchers.IO) {
run(params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import android.view.View
import android.widget.ImageView
import androidx.core.app.ActivityOptionsCompat
import androidx.fragment.app.FragmentActivity
import com.fernandocejas.sample.core.extension.empty
import com.fernandocejas.sample.features.login.Authenticator
import com.fernandocejas.sample.features.login.LoginActivity
import com.fernandocejas.sample.features.movies.MovieDetailsActivity
import com.fernandocejas.sample.features.movies.MovieView
import com.fernandocejas.sample.features.movies.MoviesActivity
import com.fernandocejas.sample.core.extension.empty
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -37,7 +37,8 @@ import javax.inject.Singleton
class Navigator
@Inject constructor(private val authenticator: Authenticator) {

private fun showLogin(context: Context) = context.startActivity(LoginActivity.callingIntent(context))
private fun showLogin(context: Context) =
context.startActivity(LoginActivity.callingIntent(context))

fun showMain(context: Context) {
when (authenticator.userLoggedIn()) {
Expand All @@ -46,13 +47,14 @@ class Navigator
}
}

private fun showMovies(context: Context) = context.startActivity(MoviesActivity.callingIntent(context))
private fun showMovies(context: Context) =
context.startActivity(MoviesActivity.callingIntent(context))

fun showMovieDetails(activity: FragmentActivity, movie: MovieView, navigationExtras: Extras) {
val intent = MovieDetailsActivity.callingIntent(activity, movie)
val sharedView = navigationExtras.transitionSharedElement as ImageView
val activityOptions = ActivityOptionsCompat
.makeSceneTransitionAnimation(activity, sharedView, sharedView.transitionName)
.makeSceneTransitionAnimation(activity, sharedView, sharedView.transitionName)
activity.startActivity(intent, activityOptions.toBundle())
}

Expand All @@ -70,7 +72,10 @@ class Navigator
private fun createYoutubeIntent(videoUrl: String): Intent {
val videoId = when {
videoUrl.startsWith(VIDEO_URL_HTTP) -> videoUrl.replace(VIDEO_URL_HTTP, String.empty())
videoUrl.startsWith(VIDEO_URL_HTTPS) -> videoUrl.replace(VIDEO_URL_HTTPS, String.empty())
videoUrl.startsWith(VIDEO_URL_HTTPS) -> videoUrl.replace(
VIDEO_URL_HTTPS,
String.empty()
)
else -> videoUrl
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import javax.inject.Inject
@AndroidEntryPoint
class RouteActivity : AppCompatActivity() {

@Inject internal lateinit var navigator: Navigator
@Inject
internal lateinit var navigator: Navigator

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ abstract class BaseActivity : AppCompatActivity() {
}

private fun addFragment(savedInstanceState: Bundle?) =
savedInstanceState ?: supportFragmentManager.inTransaction { add(R.id.fragmentContainer, fragment()) }
savedInstanceState ?: supportFragmentManager.inTransaction {
add(
R.id.fragmentContainer,
fragment()
)
}

abstract fun fragment(): BaseFragment
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ abstract class BaseFragment : Fragment() {

abstract fun layoutId(): Int

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(layoutId(), container, false)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
inflater.inflate(layoutId(), container, false)

open fun onBackPressed() {}

Expand All @@ -51,12 +55,16 @@ abstract class BaseFragment : Fragment() {
internal fun hideProgress() = progressStatus(View.GONE)

private fun progressStatus(viewStatus: Int) =
with(activity) { if (this is BaseActivity) this.progress.visibility = viewStatus }
with(activity) { if (this is BaseActivity) this.progress.visibility = viewStatus }

internal fun notify(@StringRes message: Int) =
Snackbar.make(viewContainer, message, Snackbar.LENGTH_SHORT).show()
Snackbar.make(viewContainer, message, Snackbar.LENGTH_SHORT).show()

internal fun notifyWithAction(@StringRes message: Int, @StringRes actionText: Int, action: () -> Any) {
internal fun notifyWithAction(
@StringRes message: Int,
@StringRes actionText: Int,
action: () -> Any
) {
val snackBar = Snackbar.make(viewContainer, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(actionText) { _ -> action.invoke() }
snackBar.setActionTextColor(ContextCompat.getColor(appContext, color.colorTextPrimary))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ abstract class BaseViewModel : ViewModel() {
private val _failure: MutableLiveData<Failure> = MutableLiveData()
val failure: LiveData<Failure> = _failure

protected fun handleFailure(failure: Failure) { _failure.value = failure }
protected fun handleFailure(failure: Failure) {
_failure.value = failure
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,29 @@ interface KParcelable : Parcelable {

// Creator factory functions
inline fun <reified T> parcelableCreator(crossinline create: (Parcel) -> T) =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel) = create(source)
override fun newArray(size: Int) = arrayOfNulls<T>(size)
}
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel) = create(source)
override fun newArray(size: Int) = arrayOfNulls<T>(size)
}

inline fun <reified T> parcelableClassLoaderCreator(crossinline create: (Parcel, ClassLoader) -> T) =
object : Parcelable.ClassLoaderCreator<T> {
override fun createFromParcel(source: Parcel, loader: ClassLoader) = create(source, loader)
override fun createFromParcel(source: Parcel) = createFromParcel(source, T::class.java.classLoader!!
)
override fun newArray(size: Int) = arrayOfNulls<T>(size)
}
object : Parcelable.ClassLoaderCreator<T> {
override fun createFromParcel(source: Parcel, loader: ClassLoader) = create(source, loader)
override fun createFromParcel(source: Parcel) = createFromParcel(
source, T::class.java.classLoader!!
)

override fun newArray(size: Int) = arrayOfNulls<T>(size)
}

// Parcel extensions

inline fun Parcel.readBoolean() = readInt() != 0

inline fun Parcel.writeBoolean(value: Boolean) = writeInt(if (value) 1 else 0)

inline fun <reified T : Enum<T>> Parcel.readEnum() = readInt().let { if (it >= 0) enumValues<T>()[it] else null }
inline fun <reified T : Enum<T>> Parcel.readEnum() =
readInt().let { if (it >= 0) enumValues<T>()[it] else null }

inline fun <T : Enum<T>> Parcel.writeEnum(value: T?) = writeInt(value?.ordinal ?: -1)

Expand All @@ -72,7 +75,8 @@ fun Parcel.writeDate(value: Date?) = writeNullable(value) { writeLong(it.time) }

fun Parcel.readBigInteger() = readNullable { BigInteger(createByteArray()) }

fun Parcel.writeBigInteger(value: BigInteger?) = writeNullable(value) { writeByteArray(it.toByteArray()) }
fun Parcel.writeBigInteger(value: BigInteger?) =
writeNullable(value) { writeByteArray(it.toByteArray()) }

fun Parcel.readBigDecimal() = readNullable { BigDecimal(BigInteger(createByteArray()), readInt()) }

Expand All @@ -81,6 +85,8 @@ fun Parcel.writeBigDecimal(value: BigDecimal?) = writeNullable(value) {
writeInt(it.scale())
}

fun <T : Parcelable> Parcel.readTypedObjectCompat(c: Parcelable.Creator<T>) = readNullable { c.createFromParcel(this) }
fun <T : Parcelable> Parcel.readTypedObjectCompat(c: Parcelable.Creator<T>) =
readNullable { c.createFromParcel(this) }

fun <T : Parcelable> Parcel.writeTypedObjectCompat(value: T?, parcelableFlags: Int) = writeNullable(value) { it.writeToParcel(this, parcelableFlags) }
fun <T : Parcelable> Parcel.writeTypedObjectCompat(value: T?, parcelableFlags: Int) =
writeNullable(value) { it.writeToParcel(this, parcelableFlags) }
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class NetworkHandler
val connectivityManager = context.connectivityManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network= connectivityManager.activeNetwork ?: return false
val network = connectivityManager.activeNetwork ?: return false
val activeNetwork =
connectivityManager.getNetworkCapabilities(network) ?: return false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import javax.inject.Singleton

@Singleton
class Authenticator
@Inject constructor(){
@Inject constructor() {
//Learning purpose: We assume the user is always logged in
//Here you should put your own logic to return whether the user
//is authenticated or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
package com.fernandocejas.sample.features.login

import com.fernandocejas.sample.core.platform.BaseFragment
import com.fernandocejas.sample.R
import com.fernandocejas.sample.core.platform.BaseFragment

class LoginFragment : BaseFragment() {
override fun layoutId() = R.layout.fragment_login
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import com.fernandocejas.sample.features.movies.GetMovieDetails.Params
import javax.inject.Inject

class GetMovieDetails
@Inject constructor(private val moviesRepository: MoviesRepository) : UseCase<MovieDetails, Params>() {
@Inject constructor(private val moviesRepository: MoviesRepository) :
UseCase<MovieDetails, Params>() {

override suspend fun run(params: Params) = moviesRepository.movieDetails(params.id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ package com.fernandocejas.sample.features.movies

import com.fernandocejas.sample.core.extension.empty

data class MovieDetails(val id: Int,
val title: String,
val poster: String,
val summary: String,
val cast: String,
val director: String,
val year: Int,
val trailer: String) {
data class MovieDetails(
val id: Int,
val title: String,
val poster: String,
val summary: String,
val cast: String,
val director: String,
val year: Int,
val trailer: String
) {

companion object {
val empty = MovieDetails(0, String.empty(), String.empty(), String.empty(),
String.empty(), String.empty(), 0, String.empty())
val empty = MovieDetails(
0, String.empty(), String.empty(), String.empty(),
String.empty(), String.empty(), 0, String.empty()
)
}
}
Loading