Skip to content

Commit

Permalink
- Add read me
Browse files Browse the repository at this point in the history
- Setup home feature
- Fix graphql Errors
- Remove To do
  • Loading branch information
JabezNzomo99 committed Jan 10, 2020
1 parent ec91a81 commit 6ef65b9
Show file tree
Hide file tree
Showing 28 changed files with 541 additions and 41 deletions.
40 changes: 40 additions & 0 deletions README.md
@@ -0,0 +1,40 @@
# \[ 🚧 Work in progress 👷‍♀️⛏👷🔧️👷🔧 🚧 \] Rick and Morty App


<p align="center">
<a href="https://github.com/KotlinBy/awesome-kotlin">
<img src="https://kotlin.link/awesome-kotlin.svg" alt="Awesome Kotlin">
</a>
</p>


## Screenshots

<p align="center">
<img src="screenshots/home.png" width="270" alt="Home">
<img src="screenshots/screenshot1.png" width="300" alt="Rick And Morty App">
<img src="screenshots/screenshot2.png" width="300" alt="Rick And Morty App">
</p>

## Background

Rick and Morty is a basic application fully written in Kotlin and leverages on key android concepts to allow users to;

> View All Characters in Rick & Morty
> Search For Characters based on different criteria
> View the episodes a particular character has appeared in
## Features

- :white_check_mark: Kotlin
- :white_check_mark: MVVM Architecture
- :white_check_mark: Koin Dependency Injection
- :white_check_mark: ViewModel
- :white_check_mark: GraphQL + Coroutines
- :white_check_mark: Live data
- :white_check_mark: Navigation
- :white_check_mark: Local Persistence with Room

## Thanks
[mrcsxsiq](https://github.com/mrcsxsiq/Kotlin-Pokedex) for design inspiration based on Kotlin Pokedex
GraphQL API (https://rickandmortyapi.com/graphql)
10 changes: 9 additions & 1 deletion app/build.gradle
Expand Up @@ -32,12 +32,20 @@ android {
}
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}


dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
Expand Down
Expand Up @@ -2,18 +2,13 @@ package com.example.rickandmortyapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import org.koin.androidx.viewmodel.ext.android.viewModel

class MainActivity : AppCompatActivity() {

private val mainActivityViewModel: MainActivityViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fetchCharacters()
}

private fun fetchCharacters(){
mainActivityViewModel.fetchCharacters()
}

}

This file was deleted.

Expand Up @@ -3,10 +3,11 @@ package com.example.rickandmortyapp.data.repository
import androidx.lifecycle.LiveData
import com.example.rickandmortyapp.data.model.Character
import com.example.rickandmortyapp.data.model.Episode
import com.example.rickandmortyapp.util.Result

interface CharactersRepository {

suspend fun fetchCharacters()
suspend fun fetchCharacters():Result<Boolean>
suspend fun getCharacters():LiveData<List<Character>>
suspend fun searchCharacters(searchString: String):LiveData<List<Character>>
suspend fun getEpisodesPerCharacter(characterId:String):LiveData<List<Episode>>
Expand Down
Expand Up @@ -16,8 +16,8 @@ class CharactersRepositoryImpl(private val charactersLocalDataSource: Characters
const val DEFAULT_PAGE = 1
}

override suspend fun fetchCharacters() {
when(val result=charactersRemoteDataSource.fetchCharacters(DEFAULT_PAGE)){
override suspend fun fetchCharacters() : Result<Boolean>{
return when(val result=charactersRemoteDataSource.fetchCharacters(DEFAULT_PAGE)){
is Result.Success->{
val characters = result.data
charactersLocalDataSource.addCharacters(characters)
Expand All @@ -26,10 +26,13 @@ class CharactersRepositoryImpl(private val charactersLocalDataSource: Characters
episodesLocalDataSource.addEpisodes(character.episode)
}
}
Result.Success(true)
}
is Result.Error->{
Result.Error(result.exception)

}
else->Result.Success(false)

}
}
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/com/example/rickandmortyapp/di/Modules.kt
Expand Up @@ -3,7 +3,6 @@ package com.example.rickandmortyapp.di
import android.app.Application
import androidx.room.Room
import com.apollographql.apollo.ApolloClient
import com.example.rickandmortyapp.MainActivityViewModel
import com.example.rickandmortyapp.data.local.Database
import com.example.rickandmortyapp.data.local.dao.CharacterDao
import com.example.rickandmortyapp.data.local.dao.EpisodeDao
Expand All @@ -15,6 +14,7 @@ import com.example.rickandmortyapp.data.remote.CharactersRemoteDataSource
import com.example.rickandmortyapp.data.remote.CharactersRemoteDataSourceImpl
import com.example.rickandmortyapp.data.repository.CharactersRepository
import com.example.rickandmortyapp.data.repository.CharactersRepositoryImpl
import com.example.rickandmortyapp.ui.home.HomeFragmentViewModel
import com.example.rickandmortyapp.util.Constants
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
Expand Down Expand Up @@ -74,8 +74,8 @@ import java.util.concurrent.TimeUnit
}

val viewModelModules = module {
viewModel<MainActivityViewModel>{
MainActivityViewModel(charactersRepository = get())
viewModel<HomeFragmentViewModel>{
HomeFragmentViewModel(charactersRepository = get())
}

}
@@ -0,0 +1,56 @@
package com.example.rickandmortyapp.ui.home

import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.api.load
import coil.transform.CircleCropTransformation
import com.example.rickandmortyapp.R
import com.example.rickandmortyapp.data.model.Character
import com.example.rickandmortyapp.util.ColorUtil
import com.example.rickandmortyapp.util.ItemClickListener
import kotlinx.android.synthetic.main.row_character.view.*

class CharacterAdapter(private val characters:List<Character>,
private val itemClickListener: ItemClickListener) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>(){

class CharacterViewHolder(itemView:View, private val context: Context):RecyclerView.ViewHolder(itemView){
fun bind(character:Character, itemClickListener: ItemClickListener){
itemView.textViewCharacterName.text = character.name
itemView.textViewSpecies.text = character.species
itemView.textViewGender.text = character.gender
itemView.imageViewCharacterImage.load(character.image){
crossfade(true)
placeholder(R.drawable.ic_morty)
transformations(CircleCropTransformation())


}
itemView.characterCardView.setOnClickListener {
itemClickListener.onClick(character)
}
if(character.status!=null){
if(character.status.equals("Dead")){
itemView.characterCardView.cardBackgroundColor.withAlpha(0.7.toInt())
}
}
val characterColor = ColorUtil(context = context).getCharacterColor(character.species)
itemView.constraintLayout.background.colorFilter = PorterDuffColorFilter(characterColor, PorterDuff.Mode.SRC_ATOP )
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
return CharacterViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_character,parent,false), parent.context)
}

override fun getItemCount(): Int = characters.size

override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
val character = characters[position]
holder.bind(character,itemClickListener)
}
}
@@ -0,0 +1,82 @@
package com.example.rickandmortyapp.ui.home


import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import com.example.rickandmortyapp.R
import com.example.rickandmortyapp.data.model.Character
import com.example.rickandmortyapp.util.ItemClickListener
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home_fragment.*
import org.koin.androidx.viewmodel.ext.android.viewModel

/**
* A simple [Fragment] subclass.
*/
class HomeFragment : Fragment() {

private val homeFragmentViewModel:HomeFragmentViewModel by viewModel()
private lateinit var layout:View

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
layout = inflater.inflate(R.layout.fragment_home_fragment, container, false)
return layout
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setUpViewModel()
}

private fun setUpViewModel(){
homeFragmentViewModel.isFetching.observe(this, Observer {isFetching->
when(isFetching){
true->{}
false->{}
}
})

homeFragmentViewModel.dataFetchState.observe(this, Observer {dataFetchState->
when(dataFetchState){
true->{
Snackbar.make(layout,"Characters Fetched", Snackbar.LENGTH_SHORT).show()
}
false->{
Snackbar.make(layout,"An Error Occurred", Snackbar.LENGTH_SHORT).show()

}
}
})

homeFragmentViewModel.fetchCharacters()

homeFragmentViewModel.getAllCharacters().observe(this, Observer {characters->
if(!characters.isNullOrEmpty()){
setUpRecyclerView(characters)
}

})

}

private fun setUpRecyclerView(characters:List<Character>){
val adapter = CharacterAdapter(characters, object:ItemClickListener{
override fun onClick(character: Character) {
Toast.makeText(activity, character.name, Toast.LENGTH_SHORT).show()
}
})
recyclerView.adapter = adapter
val layoutManager = GridLayoutManager(context, 2)
recyclerView.layoutManager = layoutManager
}
}
@@ -0,0 +1,33 @@
package com.example.rickandmortyapp.ui.home

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.example.rickandmortyapp.data.repository.CharactersRepository
import com.example.rickandmortyapp.util.Result
import kotlinx.coroutines.launch

class HomeFragmentViewModel(private val charactersRepository: CharactersRepository): ViewModel(){

val dataFetchState = MutableLiveData<Boolean>()
val isFetching = MutableLiveData<Boolean>()


fun getAllCharacters() = liveData {
isFetching.postValue(true)
emitSource(charactersRepository.getCharacters())
isFetching.postValue(false)
}

fun fetchCharacters() = viewModelScope.launch {
when(val result=charactersRepository.fetchCharacters()){
is Result.Success->{
dataFetchState.value = result.data
}
is Result.Error ->{
dataFetchState.value = false
}
}
}
}
27 changes: 27 additions & 0 deletions app/src/main/java/com/example/rickandmortyapp/util/ColorUtil.kt
@@ -0,0 +1,27 @@
package com.example.rickandmortyapp.util

import android.content.Context
import android.graphics.Color
import androidx.core.content.ContextCompat
import com.example.rickandmortyapp.R

class ColorUtil(private val context:Context){


fun getCharacterColor(typeOfCharacter:String?): Int {
val color = when (typeOfCharacter?.toLowerCase()) {
"human" -> R.color.lightTeal
"humanoid" -> R.color.lightRed
"animal" -> R.color.lightBlue
"alien","vampire" -> R.color.lightYellow
"poopybutthole" -> R.color.lightPurple
"mytholog" -> R.color.lightBrown
else -> return R.color.lightBlue
}
return covertColor(color)
}

fun covertColor(color: Int): Int {
return Color.parseColor("#" + Integer.toHexString(ContextCompat.getColor(context, color)))
}
}
@@ -0,0 +1,7 @@
package com.example.rickandmortyapp.util

import com.example.rickandmortyapp.data.model.Character

interface ItemClickListener {
fun onClick(character:Character)
}

0 comments on commit 6ef65b9

Please sign in to comment.