GithubHelp home page GithubHelp logo

mitchtabian / open-api-android-app Goto Github PK

View Code? Open in Web Editor NEW
687.0 26.0 228.0 2.06 MB

Kotlin, MVI, Hilt, Retrofit2, Coroutines, Room Persistence, REST API, Token Authentication

Kotlin 100.00%
kotlin dagger2 retrofit2 coroutines-android room-persistence mvvm-architecture rest-api navigation-component jetpack-navigation jetpack-android

open-api-android-app's Introduction

"Clean" Refactor Notes

In July 2021 I did a major refactor. Here's what I did:

  1. Migrate from Dagger to Hilt.
  2. Update Navigation Component.
  3. Decoupling.
    • Avoid sharing viewmodels. It makes unit testing easier when I can test fragments in isolation.
  4. Splitting business models into Entities and Dto's. This way I have a clear business model, network model, and caching model.
  5. Writing use cases.
    • Unidirectional data flow with MVI and kotlin sealed classes. (See Interactors)
  6. Refactor message handling system to a Queue.
  7. Migrate from Shared Preferences to DataStore.
  8. Migrate from Kotlin synthetics to ViewBinding.
  9. Write Unit tests for use-cases.

TODO

  1. Check out the new splash screen APIs
  2. Do a Compose refactor
    • I will create a new repo for this

Test Account

email: [email protected]

password: password


Watch the video course here: Powerful Android Apps with Jetpack Architecture.

In this course you'll learn to build a real application that interacts with the website open-api.xyz.

Open-api.xyz is a sandbox website for codingwithmitch members to practice interacting with a Rest API.


Prerequisites (Recommended not required):

  1. Dagger 2

  2. MVI Architecture

  3. Database Caching (Retrofit + Room)


What you'll learn:

  • Kotlin:
  • Coroutines:
    1. Advanced coroutine management using jobs
    2. Cancelling active jobs
    3. Coroutine scoping
  • Navigation Components:
    1. Bottom Navigation View with fragments
    2. Leveraging multiple navigation graphs (this is cutting edge content)
  • Dagger 2:
    1. custom scopes, fragment injection, activity injection, Viewmodel injection
  • MVI architecture:
    1. Basically this is MVVM with some additions
    2. State management
    3. Building a generic BaseViewModel
    4. Repository pattern (NetworkBoundResource)
  • Room Persistence:
    1. SQLite on Android with Room Persistence library
    2. Custom queries, inserts, deletes, updates
    3. Foreign Key relationships
    4. Multiple database tables
  • Cache:
    1. Database caching (saving data from network into local cache)
    2. Single source of truth principal
  • Retrofit 2:
    1. Handling any type of response from server (success, error, none, etc...)
    2. Returning LiveData from Retrofit calls (Retrofit Call Adapter)
  • ViewModels:
    1. Sharing a ViewModel between several fragments
    2. Building a powerful generic BaseViewModel
  • WebViews:
    1. Interacting with the server through a webview (Javascript)
  • SearchView:
    1. Programmatically implement a SearchView
    2. Execute search queries to network and db cache
  • Images:
    1. Selecting images from phone memory
    2. Cropping images to a specific aspect ratio
    3. Setting limitations on image size and aspect ratio
    4. Uploading a cropped image to server
  • Network Request Management:
    1. Cancelling pending network requests (Kotlin coroutines)
    2. Testing for network delays
  • Pagination:
    1. Paginating objects returned from server and database cache
  • Material Design:
    1. Bottom Navigation View with Fragments
    2. Customizing Bottom Navigation Icon behavior
    3. Handling Different Screen Sizes (ConstraintLayout)
    4. Material Dialogs
    5. Fragment transition animations

open-api-android-app's People

Contributors

erlanamanatov avatar freedomchuks avatar mitchtabian avatar morons avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

open-api-android-app's Issues

Navigating bug (Destination does not exist in nav controller)

Starting at version nav components versions 2.1.0-alpha03 my code will crash the app by doing the following:

  1. Nav to BlogFragment
  2. Press back
  3. Nav to BlogFragment

The problem seems to be the childFragmentManager. If you pop from there it removes the graph along with the last fragment, which causes the "Destination not part of this NavGraph" exception when you then try to navigate forward again.

Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\NetworkBoundResource.kt: (37, 21): Not enough information to infer type variable ViewState

Task :app:preBuild UP-TO-DATE
Task :app:preDebugBuild UP-TO-DATE
Task :app:compileDebugAidl NO-SOURCE
Task :app:compileDebugRenderscript NO-SOURCE
Task :app:generateDebugBuildConfig UP-TO-DATE
Task :app:writeDebugApplicationId UP-TO-DATE
Task :app:generateSafeArgsDebug UP-TO-DATE
Task :app:checkDebugAarMetadata UP-TO-DATE
Task :app:generateDebugResValues UP-TO-DATE
Task :app:generateDebugResources UP-TO-DATE
Task :app:mergeDebugResources UP-TO-DATE
Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
Task :app:extractDeepLinksDebug UP-TO-DATE
Task :app:processDebugMainManifest
Task :app:processDebugManifest
Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
Task :app:mergeDebugShaders UP-TO-DATE
Task :app:compileDebugShaders NO-SOURCE
Task :app:generateDebugAssets UP-TO-DATE
Task :app:mergeDebugAssets UP-TO-DATE
Task :app:compressDebugAssets UP-TO-DATE
Task :app:processDebugJavaRes NO-SOURCE
Task :app:checkDebugDuplicateClasses UP-TO-DATE
Task :app:desugarDebugFileDependencies
Task :app:mergeDebugJniLibFolders UP-TO-DATE
Task :app:mergeDebugNativeLibs UP-TO-DATE
Task :app:stripDebugDebugSymbols NO-SOURCE
Task :app:validateSigningDebug UP-TO-DATE
Task :app:mergeLibDexDebug
Task :app:processDebugManifestForPackage
Task :app:writeDebugAppMetadata UP-TO-DATE
Task :app:writeDebugSigningConfigVersions
Task :app:mergeExtDexDebug
Task :app:processDebugResources
Task :app:kaptGenerateStubsDebugKotlin UP-TO-DATE
Task :app:kaptDebugKotlin UP-TO-DATE

Task :app:compileDebugKotlin FAILED
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\NetworkBoundResource.kt: (36, 17): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\NetworkBoundResource.kt: (37, 21): Not enough information to infer type variable ViewState
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\NetworkBoundResource.kt: (47, 21): Not enough information to infer type variable ViewState
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\NetworkBoundResource.kt: (58, 25): Not enough information to infer type variable ViewState
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\auth\AuthRepositoryImpl.kt: (119, 13): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\auth\AuthRepositoryImpl.kt: (120, 17): Not enough information to infer type variable ViewState
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\auth\AuthRepositoryImpl.kt: (212, 13): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\repository\auth\AuthRepositoryImpl.kt: (213, 17): Not enough information to infer type variable ViewState
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\ui\auth\AuthViewModel.kt: (62, 35): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\ui\main\account\AccountViewModel.kt: (69, 39): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\ui\main\blog\viewmodel\BlogViewModel.kt: (45, 13): Type mismatch: inferred type is String? but String was expected
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\ui\main\blog\viewmodel\BlogViewModel.kt: (150, 43): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\ui\main\create_blog\CreateBlogViewModel.kt: (66, 39): Not enough information to infer type variable T
e: D:\android_code_repo\latest arch\Open-API-Android-App-MVI\app\src\main\java\com\codingwithmitch\openapi\util\BottomNavController.kt: (116, 27): Unresolved reference: backStack

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':app:compileDebugKotlin'.

Compilation error. See log for more details

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

  • Get more help at https://help.gradle.org

BUILD FAILED in 30s
27 actionable tasks: 9 executed, 18 up-to-date

Backstack is not maintaining properly in bottom navigation

I have noticed that backstack of bottom view is not handled properly. If you navigate multiple times in bottom navigation you have duplicated fragment in back stack

How to reproduce

Home -> Account -> create Blog -> Home -> Account

paging some times doesn't work

so as i said paging doesn't work sometime for example:
i turn on airplane mode and try to paging and i dont get new item which is correct by now b/c i dont have any network but after turning off airplane mode which it should paging works but it doesnt and i have to refersh list(swipe refresh) to paging work again, maybe we need a listener for checking network to update onUpdateQueryExhausted()?

Restore Search Bar State

In the Blog Fragment I noticed the blog search text disappears during a configuration change even through the search query is restored.

Steps to reproduce..

  • Login and land on the Blog Fragment.
  • Perform a search via the tool bar
  • rotate device
  • notice the search text in the toolbar disappears.

Great course and app btw!

Just clarification

Instead of adding all fragment in MainFragmentBuildersModule. Can we add

  1. BaseAccountFragment
  2. BaseBlogFragment
  3. BaseCreateBlogFragment

image

Multiple API Calls issue

Hi Mitch,
An Awesome app, learned a lot from your course.

I am creating a movies details app using your concepts, but i am facing one issue to make multiple API calls. When i am trying to make 2 continuous API calls, API call 1 get interrupted and and i only get result from 2nd API call.

Your help will be appreciated. Thanks!

some serious issue in viewModel

so when i try to multi trigger events it doesnt work correctly, for example i was testing BlogViewModel in init method i called some onTriggerEvent on purpose, then i find out list sets empty, why this happened? what we should do to solve that?

No injector factory bound for Class error

i get Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<com.example.openApi.ui.auth.LauncherFragment> in dagger 2.35.1 .
what should i do? i get it when app launched

Not enough information to infer type variable T

so when i use emit for buildError then i get: Not enough information to infer type variable T

for example in AuthRepositoryImpl.kt:

        emit(
            buildError(
                loginFieldErrors,
                UIComponentType.Dialog(),
                stateEvent
            )
        )

why this happening?

Toolbar searchView Cancel Button Clicked not working

There is No setOnClickListener for the search_close_btn
So here it is!
Please Amend!

		// Cancel Button Clicked!
		val cancelButton = searchView.findViewById(R.id.search_close_btn) as View
		cancelButton.setOnClickListener {
			searchPlate.setText("")
			val searchQuery = searchPlate.text.toString()
			Log.e(Constants.GLOBAL_TAG, "SearchView: (cancel button) executing search...: $searchQuery")
			executeNewQuery(searchQuery)
			searchView.isIconified = true
		}
	

there is no saving fields value in new source

so in register and login fragments(old source) it used to be save fields while destroying view:
(old source)

override fun onDestroyView() {
        super.onDestroyView()
        saveLoginFields()
    }
private fun saveLoginFields(){
        viewModel.setLoginFields(
            LoginFields(
                input_email.text.toString(),
                input_password.text.toString()
            )
        )
    }

well as u see after destroying fragment it doesnt work anymore(new source),then i saw this:
(new source)

        viewModel.state.value?.let { state ->
            setLoginFields(email = state.email, password = state.password)
            Log.i(TAG, "onViewCreated: \nstate.email:${state.email} \n  state.password:${ state.password}")
        }

and i tried to get log from it which it never showed me any email or password, it seems useless,isn't?
maybe it need to define viewModel in BaseAuthFragment?

nothing show for having no result item on list

i mean i tried to search made up word to get no result on purpose, after getting no result it has to show me there is no result to show to user(image or a textview ...) !!! but it just a blank fragment

multi dialog popup while offline paging(new source)

as u see this dialog popup(screenshot) when i get data from network then i turn on airplane mode then while paging by offline too many dialogs popup which there r the same i mean they just repeat and repeat too many times and i have no clue why this happend but probably it has something do with these:

    private fun removeHeadFromQueue() {
        Log.i(TAG, "removeHeadFromQueue: ")
        state.value?.let { state ->
            try {
                val queue = state.queue
                queue.remove() // can throw exception if empty
                this.state.value = state.copy(queue = queue)
            } catch (e: Exception) {
                Log.d(TAG, "removeHeadFromQueue: Nothing to remove from DialogQueue")
            }
        }
    }

    private fun appendToMessageQueue(stateMessage: StateMessage){
        Log.i(TAG, "appendToMessageQueue: ")
        state.value?.let { state ->
            val queue = state.queue
            if(!stateMessage.doesMessageAlreadyExistInQueue(queue = queue)){
                if(!(stateMessage.response.uiComponentType is UIComponentType.None)){
                    queue.add(stateMessage)
                    this.state.value = state.copy(queue = queue)
                }
            }
        }
    }

b/c they make log too many times

Screenshot_1627025832

RecyclerView Not Updating Edits and Deletes till Swiperefresh

Can you please test Open-Api where you delete a blog, It looks like SHOULD_REFRESH is not refreshing the blogList!, I think you have missed BlogEvent.Refresh, I may be wrong
UpdateBlog Reflect Nicely on ViewBlog But does not filter through to ListBlog

duplicate dialog

so sometimes i get duplicate dialog, i got logging and it seems queue list returns duplicate in observers, why is that? how should i to resolve that?

Question to acthitecture: Where to put potential workers?

I really like this repository, and it's architecture and project-structure. Currently, I am trying to implement "clean-achitecture" to one of my projects and stumbled upon one question: Where to put all the worker classes? For example, let's look at the following CoroutineWorker:

@HiltWorker
class DocumentListWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val firebaseEntity: DocumentRepository,
    private val documentDao: DocumentDao,
    private val networkMapper: DocumentNetworkMapper,
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        Timber.d("Started DocumentWorker")
        // Get Data from Cloud Firestore and map it to a DocumentCacheEntity to insert it to the database
        val documentEntityList = firebaseEntity.getAllDocuments()
        val documentCacheList = networkMapper.mapFromEntityList(documentEntityList)

        documentDao.deleteAll()
        documentDao.insert(documentCacheList)

        return Result.success()
    }
}

My question is: In which package would such a class fit the best? Here are my thoughts:

  1. Not into presentation, because it has nothing to do with ui
  2. Not into domain because it does not represent any sort of domain-data
  3. Not into interactors
  4. Somewhere into datasource, maybe into network as business -> datasource -> network -> worker OR
  5. As another package inside datasource, so business -> datasource -> worker?

I appreciate any answer, thank you very much

Crash when selecting "Home" tab

11-30 04:54:27.700 4977-4977/com.codingwithmitch.openapi E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.codingwithmitch.openapi, PID: 4977
    java.lang.NullPointerException: Attempt to invoke virtual method 'com.codingwithmitch.openapi.ui.Data com.codingwithmitch.openapi.ui.DataState.getData()' on a null object reference
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:79)
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:21)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.Transformations$2$1.onChanged(Transformations.java:155)
        at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Deleted posts still exists on other user devices and displays error on viewing.

If the author deleted a post, ideally it should not be seen on the other user devices as well.
For example, deleting a Instagram post. The post is not view-able to other users if the author deletes it.

But, the deleted post is stored in cache in the Open API App case. This is definitely an undesirable behavior and a bug.

It would be helpful if you take action to solve this issue.

Steps to reproduce:

  1. Create a post with device 'A', use emulator or device 'B' and sign in with same user and delete the post.
  2. Go back to device 'A' and click on the post we just deleted using device 'B'.

[1]
unnamed (1)

[2]
unnamed

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.