text:
<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.databinding.HeroSquadItemBinding

class SquadAdapter : RecyclerView.Adapter<SquadAdapter.HeroViewHolder>() {
    inner class HeroViewHolder(val binding: HeroSquadItemBinding) :
        RecyclerView.ViewHolder(binding.root)

    private val differCallback = object : DiffUtil.ItemCallback<Result>() {
        override fun areItemsTheSame(oldItem: Result, newItem: Result): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Result, newItem: Result): Boolean {
            return oldItem == newItem
        }
    }
    val differ = AsyncListDiffer(this, differCallback)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeroViewHolder {
        return HeroViewHolder(
            HeroSquadItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: HeroViewHolder, position: Int) {
        val hero = differ.currentList[position]
        val binding = holder.binding

        holder.itemView.apply {
            Glide.with(this).load(hero.thumbnail?.getURL()).into(binding.image)
            binding.name.text = hero.name
            setOnClickListener {
                onItemClickListener?.let { it(hero) }
            }
        }
    }

    override fun getItemCount(): Int {
        return differ.currentList.size
    }

    private var onItemClickListener: ((Result) -> Unit)? = null

    fun setOnItemClickListener(listener: (Result) -> Unit) {
        onItemClickListener = listener
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.allHeroes

import kotlinx.coroutines.ExperimentalCoroutinesApi

@ExperimentalCoroutinesApi
class AllHeroesViewModelTest<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.data.api

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class Thumbnail(
    val extension: String? = null,
    val path: String? = null
) : Parcelable {
    fun getURL(): String {
        return if (this.extension == this.path) {
            "${this.path}"
        } else {
            "${this.path}.${this.extension}"
        }
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.di

import android.content.Context
import androidx.room.Room
import com.userfaltakas.marvelheroesdagger_hilt.api.HeroesAPI
import com.userfaltakas.marvelheroesdagger_hilt.constant.Constants.BASE_URL
import com.userfaltakas.marvelheroesdagger_hilt.constant.Constants.DATABASE_NAME
import com.userfaltakas.marvelheroesdagger_hilt.database.HeroDao
import com.userfaltakas.marvelheroesdagger_hilt.database.HeroDatabase
import com.userfaltakas.marvelheroesdagger_hilt.repository.DefaultHeroesRepository
import com.userfaltakas.marvelheroesdagger_hilt.repository.HeroRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideHeroesAPI(): HeroesAPI = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(HeroesAPI::class.java)

    @Singleton
    @Provides
    fun provideHeroesDatabase(
        @ApplicationContext app: Context
    ): HeroDatabase = Room.databaseBuilder(
        app,
        HeroDatabase::class.java,
        DATABASE_NAME
    ).build()

    @Singleton
    @Provides
    fun provideRunDao(db: HeroDatabase): HeroDao = db.getHeroDao()

    @Singleton
    @Provides
    fun provideDefaultHeroesRepository(heroDao: HeroDao, heroesAPI: HeroesAPI) =
        DefaultHeroesRepository(heroDao, heroesAPI) as HeroRepository
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.repository

import com.userfaltakas.marvelheroesdagger_hilt.api.Resource
import com.userfaltakas.marvelheroesdagger_hilt.data.api.HeroesResponse
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result

class FakeHeroesRepositoryTest : HeroRepository {
    private val heroes = mutableListOf<Result>()
    private var shouldReturnNetworkError = false

    fun setShouldReturnNetworkError(value: Boolean) {
        shouldReturnNetworkError = value
    }

    override suspend fun getHeroes(offset: Int): Resource<HeroesResponse> {
        return if (shouldReturnNetworkError) {
            Resource.Error("Error", null)
        } else {
            Resource.Success(HeroesResponse(200, null, "OK"))
        }
    }

    override suspend fun addHero(result: Result): Long {
        heroes.add(result)
        return result.id.toLong()
    }

    override suspend fun getSquad(): List<Result> {
        return heroes
    }

    override suspend fun removeHero(result: Result) {
        heroes.remove(result)
    }

    override suspend fun isHeroExist(id: Int): Boolean {
        heroes.forEach {
            if (it.id == id) {
                return true
            }
        }
        return false
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.repository

import com.userfaltakas.marvelheroesdagger_hilt.api.HeroesAPI
import com.userfaltakas.marvelheroesdagger_hilt.api.Resource
import com.userfaltakas.marvelheroesdagger_hilt.constant.Constants
import com.userfaltakas.marvelheroesdagger_hilt.data.api.HeroesResponse
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.database.HeroDao
import javax.inject.Inject

class DefaultHeroesRepository @Inject constructor(
    val heroDao: HeroDao,
    private val heroesApi: HeroesAPI
) : HeroRepository {
    override suspend fun getHeroes(offset: Int): Resource<HeroesResponse> {
        val filter = mutableMapOf<String, String>()
        filter["ts"] = Constants.ts
        filter["apikey"] = Constants.API_KEY
        filter["hash"] = Constants.hash()
        filter["offset"] = offset.toString()
        val response = heroesApi.getHeroes(filter)
        if (response.isSuccessful) {
            response.body()?.let {
                return Resource.Success(it)
            }
        }
        return Resource.Error(response.message())
    }

    override suspend fun addHero(result: Result) = heroDao.insert(result)
    override suspend fun getSquad() = heroDao.getHeroes()
    override suspend fun removeHero(result: Result) = heroDao.delete(result)
    override suspend fun isHeroExist(id: Int) = heroDao.isHeroExist(id)
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.data.api

import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize

@Entity(tableName = "squad")
@Parcelize
data class Result(
    @PrimaryKey(autoGenerate = false)
    val id: Int,
    val description: String? = null,
    val name: String? = null,
    val thumbnail: Thumbnail? = null,
) : Parcelable<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.data.api

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class HeroesResponse(
    val code: Int? = null,
    val data: Data? = null,
    val status: String? = null
) : Parcelable<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.data.ui

enum class ButtonState {
    FIRE, HIRE
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.database

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result

@Database(
    entities = [Result::class],
    version = 1
)
@TypeConverters(Converters::class)
abstract class HeroDatabase : RoomDatabase() {
    abstract fun getHeroDao(): HeroDao
}
<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.heroesPreview

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.androiddevs.shoppinglisttestingyt.getOrAwaitValue
import com.google.common.truth.Truth.assertThat
import com.userfaltakas.marvelheroesdagger_hilt.MainCoroutineRule
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Thumbnail
import com.userfaltakas.marvelheroesdagger_hilt.repository.FakeHeroesRepositoryTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class HeroPreviewViewModelTest {
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()

    private lateinit var viewModel: HeroPreviewViewModel

    @Before
    fun setup() {
        viewModel = HeroPreviewViewModel(FakeHeroesRepositoryTest())
    }

    @Test
    fun `check for hero inside squad, returns true`() = runBlockingTest {
        viewModel.addHeroToSquad(Result(1, "description", "name", Thumbnail("jpeg", "url")))
        viewModel.addHeroToSquad(Result(2, "description", "name", Thumbnail("jpeg", "url")))
        viewModel.addHeroToSquad(Result(3, "description", "name", Thumbnail("jpeg", "url")))
        viewModel.isHeroExist(2)
        val response = viewModel.heroExist.getOrAwaitValue()
        assertThat(response).isEqualTo(true)
    }

    @Test
    fun `check for hero inside squad, returns false`() = runBlockingTest {
        viewModel.addHeroToSquad(Result(1, "description", "name", Thumbnail("jpeg", "url")))
        viewModel.isHeroExist(5)
        val response = viewModel.heroExist.getOrAwaitValue()
        assertThat(response).isEqualTo(false)
    }

    @Test
    fun `remove hero from squad and check if hero is inside squad, returns false`() =
        runBlockingTest {
            viewModel.addHeroToSquad(Result(1, "description", "name", Thumbnail("jpeg", "url")))
            viewModel.removeHeroFromSquad(
                Result(
                    1,
                    "description",
                    "name",
                    Thumbnail("jpeg", "url")
                )
            )
            viewModel.isHeroExist(1)
            val response = viewModel.heroExist.getOrAwaitValue()
            assertThat(response).isEqualTo(false)
        }

}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.allHeroes

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.userfaltakas.marvelheroesdagger_hilt.api.Resource
import com.userfaltakas.marvelheroesdagger_hilt.constant.Constants
import com.userfaltakas.marvelheroesdagger_hilt.data.api.HeroesResponse
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.repository.HeroRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class AllHeroesViewModel @Inject constructor(
    private val heroesRepository: HeroRepository
) : ViewModel() {

    val heroes: MutableLiveData<Resource<HeroesResponse>> = MutableLiveData()
    private var heroesResponse: HeroesResponse? = null
    private var offset = 0
    private var total = 0
    val squad: MutableLiveData<List<Result>> = MutableLiveData()

    fun getHeroes() = viewModelScope.launch {
        heroes.postValue(Resource.Loading())
        val response = heroesRepository.getHeroes(offset)
        heroes.postValue(handleHeroPaging(response))
    }

    private fun handleHeroPaging(response: Resource<HeroesResponse>): Resource<HeroesResponse> {
        when (response) {
            is Resource.Success -> {
                offset += Constants.PAGE_OFFSET
                if (heroesResponse == null) {
                    heroesResponse = response.data
                    total = response.data?.data?.total!!
                } else {
                    val oldHeroes = heroesResponse!!.data?.results
                    val newHeroes = response.data?.data?.results
                    if (newHeroes != null) {
                        oldHeroes?.addAll(newHeroes)
                    }
                }
                return Resource.Success(heroesResponse!!)
            }
            else ->
                return response
        }
    }

    fun isLastPage(): Boolean {
        return offset >= total
    }

    fun getSquad() = viewModelScope.launch {
        squad.postValue(heroesRepository.getSquad())
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.repository

import com.userfaltakas.marvelheroesdagger_hilt.api.Resource
import com.userfaltakas.marvelheroesdagger_hilt.data.api.HeroesResponse
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result

interface HeroRepository {
    suspend fun getHeroes(offset: Int): Resource<HeroesResponse>
    suspend fun addHero(result: Result): Long
    suspend fun getSquad(): List<Result>
    suspend fun removeHero(result: Result)
    suspend fun isHeroExist(id: Int): Boolean
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.di

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MarvelApplication : Application()<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.heroesPreview

import android.app.AlertDialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.Navigation
import androidx.navigation.fragment.navArgs
import com.bumptech.glide.Glide
import com.userfaltakas.marvelheroesdagger_hilt.R
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.data.ui.ButtonState
import com.userfaltakas.marvelheroesdagger_hilt.databinding.FragmentHeroPreviewBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class HeroPreviewFragment : Fragment() {
    private var _binding: FragmentHeroPreviewBinding? = null
    private val binding get() = _binding!!
    private val viewModel by activityViewModels<HeroPreviewViewModel>()
    private lateinit var hero: Result
    private lateinit var hireBtnState: ButtonState
    private val args: HeroPreviewFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHeroPreviewBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        binding.closeBtn.setOnClickListener {
            val action =
                HeroPreviewFragmentDirections.actionHeroPreviewFragmentToAllHeroesFragment()
            Navigation.findNavController(requireView()).navigate(action)
        }

        binding.hireBtn.setOnClickListener {
            if (hireBtnState == ButtonState.HIRE) {
                viewModel.addHeroToSquad(args.hero)
                fireHero()
            } else {
                fireHeroAlertDialog().show()
            }
        }
    }

    private fun fireHeroAlertDialog(): AlertDialog {
        return AlertDialog.Builder(requireContext())
            .setTitle(resources.getString(R.string.are_you_sure))
            .setMessage(resources.getString(R.string.do_you_want_to_fire_hero))
            .setPositiveButton(resources.getString(R.string.yes)) { _, _ ->
                viewModel.removeHeroFromSquad(args.hero)
                hireHero()
            }
            .setNegativeButton(resources.getString(R.string.no)) { _, _ ->
            }.create()
    }

    private fun fireHero() {
        binding.hireBtn.apply {
            setBackgroundColor(
                ContextCompat.getColor(
                    requireContext(),
                    R.color.red
                )
            )
            text = getString(R.string.fire)
            hireBtnState = ButtonState.FIRE
        }
    }

    private fun hireHero() {
        binding.hireBtn.apply {
            setBackgroundColor(
                ContextCompat.getColor(
                    requireContext(),
                    R.color.purple_500
                )
            )

            text = getString(R.string.hire)
            hireBtnState = ButtonState.HIRE
        }
    }

    private fun setContext() {
        hero = args.hero
        setButton(hero.id)
        binding.apply {
            heroName.text = hero.name
            description.text = hero.description
            Glide.with(requireContext()).load(hero.thumbnail?.getURL()).into(image)
        }
    }

    private fun setButton(id: Int) {
        viewModel.isHeroExist(id)
        viewModel.heroExist.observe(viewLifecycleOwner, { response ->
            if (response) {
                fireHero()
            } else {
                hireHero()
            }
        })
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.activity

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.Navigation
import com.userfaltakas.marvelheroesdagger_hilt.R
import com.userfaltakas.marvelheroesdagger_hilt.databinding.ActivityStartBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class StartActivity : AppCompatActivity() {
    private lateinit var binding: ActivityStartBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityStartBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.database

import androidx.room.TypeConverter
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Thumbnail

class Converters {
    @TypeConverter
    fun fromThumbnail(thumbnail: Thumbnail): String {
        return "${thumbnail.path}.${thumbnail.extension}"
    }

    @TypeConverter
    fun toThumbnail(name: String): Thumbnail {
        return Thumbnail(name, name)
    }
}
<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.heroesPreview

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result
import com.userfaltakas.marvelheroesdagger_hilt.repository.HeroRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class HeroPreviewViewModel @Inject constructor(
    private val heroesRepository: HeroRepository
) : ViewModel() {

    lateinit var heroExist: MutableLiveData<Boolean>

    fun addHeroToSquad(result: Result) = viewModelScope.launch {
        heroesRepository.addHero(result)
    }

    fun removeHeroFromSquad(result: Result) = viewModelScope.launch {
        heroesRepository.removeHero(result)
    }

    fun isHeroExist(id: Int) = viewModelScope.launch {
        heroExist = MutableLiveData()
        heroExist.postValue(heroesRepository.isHeroExist(id))
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.database

import androidx.room.*
import com.userfaltakas.marvelheroesdagger_hilt.data.api.Result

@Dao
interface HeroDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(result: Result): Long

    @Query("SELECT * FROM  squad ORDER BY name")
    suspend fun getHeroes(): List<Result>

    @Delete
    suspend fun delete(result: Result)

    @Query("SELECT EXISTS(SELECT * FROM squad WHERE id = :id)")
    suspend fun isHeroExist(id: Int): Boolean
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.ui.fragment.allHeroes

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.Navigation
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.userfaltakas.marvelheroesdagger_hilt.api.Resource
import com.userfaltakas.marvelheroesdagger_hilt.constant.Constants.PAGE_OFFSET
import com.userfaltakas.marvelheroesdagger_hilt.databinding.FragmentAllHeroesBinding
import com.userfaltakas.marvelheroesdagger_hilt.network.NetworkManager
import com.userfaltakas.marvelheroesdagger_hilt.ui.adapter.HeroesAdapter
import com.userfaltakas.marvelheroesdagger_hilt.ui.adapter.SquadAdapter
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class AllHeroesFragment : Fragment() {
    private var _binding: FragmentAllHeroesBinding? = null
    private val binding get() = _binding!!
    private val viewModel by activityViewModels<AllHeroesViewModel>()
    private lateinit var allHeroesAdapter: HeroesAdapter
    private lateinit var squadAdapter: SquadAdapter
    private var isLoading = false
    private var isLastPage = false
    private var isScrolling = false
    private var networkManager = NetworkManager()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentAllHeroesBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        if (viewModel.heroes.value == null) {
            makeHeroRequest()
        }
        setContext()
        setUpAllHeroesRecyclerView()
        setUpSquadRecyclerView()
    }

    private fun setUpSquadRecyclerView() {
        squadAdapter = SquadAdapter()
        binding.squadRecyclerView.apply {
            adapter = squadAdapter
            layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
        }
        squadAdapter.setOnItemClickListener {
            val action =
                AllHeroesFragmentDirections.actionAllHeroesFragmentToHeroPreviewFragment(
                    it
                )
            Navigation.findNavController(requireView()).navigate(action)
        }
    }

    private fun setUpAllHeroesRecyclerView() {
        allHeroesAdapter = HeroesAdapter()
        binding.allHeroesRecyclerView.apply {
            adapter = allHeroesAdapter
            layoutManager = LinearLayoutManager(activity)
            addOnScrollListener(this@AllHeroesFragment.scrollListener)
        }
        allHeroesAdapter.setOnItemClickListener {
            val action =
                AllHeroesFragmentDirections.actionAllHeroesFragmentToHeroPreviewFragment(
                    it
                )
            Navigation.findNavController(requireView()).navigate(action)
        }
    }

    private fun setContext() {
        viewModel.heroes.observe(viewLifecycleOwner, { response ->
            when (response) {
                is Resource.Success -> {
                    hideProgressBar()
                    response.data?.data?.let {
                        allHeroesAdapter.differ.submitList(it.results?.toList())
                        isLastPage = viewModel.isLastPage()
                        if (isLastPage) {
                            removeRecyclerViewPadding()
                        }
                    }
                }
                is Resource.Loading -> {
                    showProgressBar()
                }
                is Resource.Error -> {
                    response.message?.let { message ->
                        Log.e(tag, "ERROR! $message")
                    }
                    hideProgressBar()
                }
            }
        })

        viewModel.getSquad()
        viewModel.squad.observe(viewLifecycleOwner, { response ->
            if (response.isNotEmpty()) {
                showMySquad()
                response?.let {
                    squadAdapter.differ.submitList(it)
                }
            } else {
                hideMySquad()
            }
        })
    }

    private val scrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                isScrolling = true
            }
        }

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            val layoutManager = recyclerView.layoutManager as LinearLayoutManager
            var shouldPaginate: Boolean
            layoutManager.apply {
                val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
                val isAtLastItem =
                    findFirstVisibleItemPosition() + childCount >= itemCount
                val isNotAtBeginning = findFirstVisibleItemPosition() >= 0
                val isTotalMoreThanVisible = itemCount >= PAGE_OFFSET
                shouldPaginate = isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning
                        && isTotalMoreThanVisible && isScrolling
            }

            if (shouldPaginate) {
                makeHeroRequest()
                isScrolling = false
            }
        }
    }

    private fun showMySquad() {
        binding.squadTitle.visibility = View.VISIBLE
        binding.squadRecyclerView.visibility = View.VISIBLE
    }

    private fun hideMySquad() {
        binding.squadTitle.visibility = View.GONE
        binding.squadRecyclerView.visibility = View.GONE
    }

    private fun hideProgressBar() {
        binding.progressBar.visibility = View.GONE
        isLoading = false
    }

    private fun showProgressBar() {
        binding.progressBar.visibility = View.VISIBLE
        isLoading = true
    }

    private fun removeRecyclerViewPadding() {
        binding.allHeroesRecyclerView.setPadding(0, 0, 0, 0)
    }

    private fun makeHeroRequest() {
        if (networkManager.checkNetworkAvailability(requireContext())) {
            viewModel.getHeroes()
        }
    }
}<file_sep>package com.userfaltakas.marvelheroesdagger_hilt.api

import com.userfaltakas.marvelheroesdagger_hilt.data.api.HeroesResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.QueryMap

interface HeroesAPI {
    @GET("characters")
    suspend fun getHeroes(
        @QueryMap filter: Map<String, String>
    ): Response<HeroesResponse>
}

repo_language: Kotlin


text:
<file_sep>package com.ycwang.moduleapp.room;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Embedded;
import android.arch.persistence.room.Entity;

import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;

/**
 * @author ycwang.
 * @date 2018-8-27.
 */

@Entity(tableName = "user",
        indices = {@Index(value = "first_name", unique = true)})
public class User {

    @PrimaryKey(autoGenerate = true)
    public int _id;

    @ColumnInfo(name = "first_name")
    public String name;

    @Embedded
    public Address address;

    public int age;

    public int userId;

}
<file_sep>package com.ycwang.moduleapp.test;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

/**
 * @author ycwang.
 * @date 2018-9-3.
 */
public class FragmentTActivity  extends FragmentActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getSupportFragmentManager().popBackStack();
        getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {

            }
        });

    }
}
<file_sep>package com.ycwang.moduleapp.test;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.AlarmClock;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.ycwang.moduleapp.R;

import java.io.File;

/**
 * @author ycwang.
 * @date 2018-8-31.
 */
public class IntentActivity extends Activity {

    Button button;

    public static void launch(Context context) {
        Intent intent = new Intent(context, IntentActivity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent);

        button = findViewById(R.id.btn_test_intent);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showMap(Uri.parse("geo:36.500279,114.709705?z=11"));
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) {
            return;
        }
        switch (requestCode) {
            case 666:
                if (data != null) {
                    Log.e("ycwang", String.valueOf(resultCode));
                    Log.e("ycwang", data.getStringExtra("ycwang"));
                }
                break;

            case 1001:
                Bitmap thumbnail = data.getParcelableExtra("data");
                Log.e("ycwang", thumbnail.toString());
                break;

            case 1002:
            case 1003:
                Uri contactUri = data.getData();
                Log.e("ycwang", contactUri.toString());
                break;

            default:
                break;
        }
    }

//    ACTION_SETTINGS
//            ACTION_WIRELESS_SETTINGS
//    ACTION_AIRPLANE_MODE_SETTINGS
//            ACTION_WIFI_SETTINGS
//    ACTION_APN_SETTINGS
//            ACTION_BLUETOOTH_SETTINGS
//    ACTION_DATE_SETTINGS
//            ACTION_LOCALE_SETTINGS
//    ACTION_INPUT_METHOD_SETTINGS
//            ACTION_DISPLAY_SETTINGS
//    ACTION_SECURITY_SETTINGS
//            ACTION_LOCATION_SOURCE_SETTINGS
//    ACTION_INTERNAL_STORAGE_SETTINGS
//            ACTION_MEMORY_CARD_SETTINGS
//
//    public void openWifiSettings() {
//        Intent intent = new Intent(Intent.);
//        if (intent.resolveActivity(getPackageManager()) != null) {
//            startActivity(intent);
//        }
//    }

    /**
     * 显示经纬度
     *
     * @param geoLocation
     */
    public void showMap(Uri geoLocation) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(geoLocation);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }
    }

    /**
     * 选择一张图片
     */
    public void selectImage() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.setType("image/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        // Only the system receives the ACTION_OPEN_DOCUMENT, so no need to test.
        startActivityForResult(intent, 1003);
    }

    /**
     * 选择联系人
     */
    public void selectContract() {
        Intent intent = new Intent(Intent.ACTION_PICK)
                .setType(ContactsContract.Contacts.CONTENT_TYPE);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, 1002);
        }
    }

    public void capturePhotoVideo() {
        Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, 1001);
        }
    }

    /**
     * 设置闹钟
     */
    public void startAlarm() {
        Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
                .putExtra(AlarmClock.EXTRA_MESSAGE, "闹钟测试")
                .putExtra(AlarmClock.EXTRA_MINUTES, 1);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }
    }

    /**
     * 设置倒计时
     */
    public void startTimer() {
        Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER)
                .putExtra(AlarmClock.EXTRA_MESSAGE, "测试倒计时")
                .putExtra(AlarmClock.EXTRA_LENGTH, 10)
                .putExtra(AlarmClock.EXTRA_SKIP_UI, true);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }
    }

    /**
     * 显示闹钟
     */
    public void showAllAlarm() {
        Intent intent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }
    }

    /**
     * 日历提醒
     */
    public void addCalenderEvent() {
        Intent intent = new Intent(Intent.ACTION_INSERT)
                .setData(CalendarContract.Events.CONTENT_URI)
                .putExtra(CalendarContract.Events.TITLE, "ycwang")
                .putExtra(CalendarContract.Events.EVENT_LOCATION, "hello")
                .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, "2019-10-20")
                .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, "2019-10-30");

        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }
    }

    public void capturePicOrVideo() {
        File file = getFile(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "avatar", "avatar.jpg");
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
                .putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, 1001);
        }
    }

    public static File getFile(final File rootPath, final String subPath, final String fileName) {
        if (rootPath == null) {
            throw new IllegalArgumentException();
        } else if (rootPath.isFile()) {
            return rootPath;
        } else {
            final File dir = TextUtils.isEmpty(subPath) ? rootPath : new File(rootPath, subPath);
            if (TextUtils.isEmpty(fileName)) {
                if (dir.exists()) {
                    return dir;
                } else {
                    return dir.mkdirs() ? dir : null;
                }
            } else {
                if (dir.exists()) {
                    return new File(dir, fileName);
                } else {
                    return dir.mkdirs() ? new File(dir, fileName) : null;
                }
            }
        }
    }

    public void capturePhoto() {
        Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, 1001);
        }
    }

}
<file_sep>package com.ycwang.moduleapp.test;

import android.view.GestureDetector;
import android.view.MotionEvent;

import java.io.Serializable;

/**
 * @author ycwang.
 * @date 2018-9-29.
 */
public class MyUser implements Serializable {

    public static long serialVersionUID = 1L;

    public int age;

    public String name;

    public String address;

    public MyUser(int age, String name, String address) {
        this.age = age;
        this.name = name;
        this.address = address;

        new CustomerOnGesyureListener(){
            @Override
            public void onLongPress(MotionEvent e) {

            }
        };

    }

    @Override
    public String toString() {
        return "MyUser{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
<file_sep>package com.ycwang.moduleapp.annotation;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.Button;

import com.ycwang.moduleapp.R;

/**
 * @author ycwang.
 * @date 2018-8-20.
 */
public class AnnotationActivity extends Activity {

    @InjectView(R.id.bind_view_btn)
    Button buttonView;

    @InjectView(R.id.bind_click_btn)
    Button buttonClick;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annotation);
        Utils.injectView(this);

        buttonView.setText("buttonView" + "+++++++++");
        buttonClick.setText("buttonClick" + "=========");
    }

}
<file_sep>package com.ycwang.moduleapp.test;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.Button;

import com.ycwang.moduleapp.R;

/**
 * @author ycwang.
 * @date 2018-9-11.
 */
public class MessagerActivity extends Activity {

    Messenger mService = null;

    boolean mBound;

    Button btn;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = new Messenger(iBinder);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
            mBound = false;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messager);
        btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!mBound) {
                    return;
                }
                // Create and send a message to the service, using a supported 'what' value
                Message msg = Message.obtain(null, MessageService.MSG_SAY_HELLO, 0, 0);
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this, MessageService.class), serviceConnection,
                Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(serviceConnection);
            mBound = false;
        }
    }
}
<file_sep>package com.ycwang.moduleapp.retrofit;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;

/**
 * @author ycwang.
 * @date 2018-8-28.
 */
public interface IUserBiz {

    @GET("user")
    Call<List<MyUser>> getUsers();

}
<file_sep>package com.ycwang.mylib2.global;

import android.app.Application;
import android.util.Log;

import com.ycwang.moduleApp.ModuleImpl;

/**
 * @author ycwang.
 * @date 2018-8-21.
 */
public class Lib2ModuleApp implements ModuleImpl {
    @Override
    public void onLoad(Application application) {
        for (int i = 0; i < 5; i++) {
            Log.e("ycwang", "Lib2ModuleApp" + i);
        }
    }
}
<file_sep>package com.ycwang.mylib1.webUrl;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.webkit.WebView;

import com.alibaba.android.arouter.facade.annotation.Route;
import com.ycwang.global.MainConstant;
import com.ycwang.mylib1.R;

/**
 * @author ycwang.
 * @date 2018-8-20.
 */
// 模拟Web页面
@Route(path = MainConstant.WEB_MODULE_MAIN)
public class WebActivity extends Activity {

    WebView webView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);

        webView = findViewById(R.id.web);

        webView.loadData("
1:URL普通跳转
\n" +
                "\n" +
                "
guotongshiyou://ycwang/web/NoCanReceiveURLActivity \n" +
                "
\n" +
                "\n" +
                "
2:URL普通跳转携带参数
\n" +
                "\n" +
                "
\n" +
                "guotongshiyou://ycwang/web/HasCanReceiveURLActivity?name=alex&age=18&boy=true&high=180&obj={\"name\":\"jack\",\"id\":\"666\"}\n" +
                "\n" +
                "
", "text/html;charset=utf-8", "utf-8");
    }
}
<file_sep>package com.ycwang.moduleapp.test;

import android.animation.ArgbEvaluator;
import android.animation.FloatEvaluator;
import android.animation.IntEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.ycwang.moduleapp.R;
import com.ycwang.moduleapp.global.App;

/**
 * @author ycwang.
 * @date 2018-9-28.
 */
public class AnimationTestActivity extends Activity {

    TextView txt;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_test);

        txt = findViewById(R.id.txt);
        txt.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onClick(final View view) {

// 宽度在5秒内由0变为500px
//                ViewWrapper viewWrapper = new ViewWrapper(txt);
//                ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper, "width", 0, 500);
//                animator.setDuration(5000);
//                animator.start();

// 宽度在5秒内由0变为500px
//                ValueAnimator animator = ValueAnimator.ofInt(1, 100);
//                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//                    IntEvaluator evaluator = new IntEvaluator();
//
//                    @Override
//                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
//                        int currentValue = (int) valueAnimator.getAnimatedValue();
//                        Log.e("ycwang", "currentValue:" + currentValue);
//                        float fraction = valueAnimator.getAnimatedFraction();
//                        Log.e("ycwang", "fraction:" + fraction);
//                        view.getLayoutParams().width = evaluator.evaluate(fraction, 0, 500);
//                        view.requestLayout();
//                    }
//                });
//                animator.setDuration(5000);
//                animator.start();

// 背景颜色在5秒内由白色变为红色
//                ObjectAnimator animator = ObjectAnimator.ofArgb(txt, "backgroundColor", 0xfff10f0f, 0xff0f94f1, 0xffeaf804, 0xfff92a0f);
//                animator.setDuration(5000);
//                animator.setEvaluator(new ArgbEvaluator());
//                animator.start();

// 背景颜色在5秒内由白色变为红色
//                ValueAnimator animator = ValueAnimator.ofInt(0, 100);
//                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//                    ArgbEvaluator argbEvaluator = new ArgbEvaluator();
//
//                    @Override
//                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
//                        int currentValue = (int) valueAnimator.getAnimatedValue();
//                        float fraction = valueAnimator.getAnimatedFraction();
//                        txt.setBackgroundColor((Integer) argbEvaluator.evaluate(fraction, 0xffffffff, 0xffff4400));
//                    }
//                });
//                animator.setDuration(5000);
//                animator.start();

// Floating Button
                Button button = new Button(App.getTopActivity());
                button.setText("Button");
                final WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                        WindowManager.LayoutParams.WRAP_CONTENT,
                        0,
                        0,
                        PixelFormat.TRANSPARENT);
                params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//                params.gravity = Gravity.LEFT | Gravity.TOP;
                params.x = 100;
                params.y = 300;

                final WindowManager windowManager = App.getTopActivity().getWindowManager();
                button.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View view, MotionEvent motionEvent) {
                        int rawX = (int) motionEvent.getRawX();
                        int rawY = (int) motionEvent.getRawY();
                        switch (motionEvent.getAction()) {
                            case MotionEvent.ACTION_MOVE:
                                params.x = rawX;
                                params.y = rawY;
                                windowManager.updateViewLayout(view, params);
                                break;
                            default:
                                break;
                        }
                        return true;
                    }
                });
                windowManager.addView(button, params);
            }
        });

        // 测试
        String s = "abcde你好";
        EditText editText = findViewById(R.id.edx);
        editText.setText(s);
        editText.setSelection(s.length());

    }

    public class ViewWrapper {

        View view;

        public ViewWrapper(View view) {
            this.view = view;
        }

        public int getWidth() {
            return view.getLayoutParams().width;
        }

        public void setWidth(int width) {
            view.getLayoutParams().width = width;
            view.requestLayout();
        }

        public int getArgb() {
            return view.getDrawingCacheBackgroundColor();
        }

        public void setArgb(int argb) {
            view.setBackgroundColor(argb);
//            view.invalidate();
        }
    }

}
<file_sep>package com.ycwang.moduleapp.dispatchEvent.activity.view.activity;

import android.support.v4.util.ArrayMap;

import com.ycwang.moduleapp.R;
import com.ycwang.moduleapp.dispatchEvent.activity.baseManager.ModuleManagerActivity;
import com.ycwang.moduleapp.global.MainPageConfig;

import java.util.ArrayList;

/**
 * @author ycwang.
 * @date 2018-8-21.
 */
public class ModuleMainActivity extends ModuleManagerActivity {

    @Override
    public int getLayoutId() {
        return R.layout.activity_module_main;
    }

    @Override
    public ArrayMap<String, ArrayList<Integer>> moduleConfig() {
        ArrayMap<String, ArrayList<Integer>> map = new ArrayMap<>();
        map.put(MainPageConfig.MODULE_PAGE_NAME, new ArrayList<Integer>() {{
            add(R.id.page_name_main);
        }});
        map.put(MainPageConfig.MODULE_PAGE_BODY, new ArrayList<Integer>() {
            {
                add(R.id.page_bodyB);
            }

            {
                add(R.id.page_bodyT);
            }
        });
        return map;
    }
}
<file_sep>package com.ycwang.moduleapp.fragment.util;

import android.support.v4.app.Fragment;

import com.ycwang.moduleapp.global.MainPageConfig;

/**
 * @author ycwang.
 * @date 2018-8-14.
 */
public class FragmentUtils {

    public static Fragment createFragment(Integer tag) {
        try {
            String name = MainPageConfig.PATH + MainPageConfig.FRAGMENT_NAME[tag];
            Fragment fragment = (Fragment) Class.forName(name).newInstance();
            MainPageConfig.FRAGMENTS[tag] = fragment;
            return fragment;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}
<file_sep>package com.ycwang.moduleapp.global;

import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;

import com.alibaba.android.arouter.launcher.ARouter;
import com.ycwang.moduleApp.ModuleImpl;
import com.ycwang.moduleapp.BuildConfig;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author ycwang.
 * @date 2018-8-13.
 */
public class App extends Application {

    private static Activity activity;

    private static Context context;

    /**
     * 获取顶部Activity
     *
     * @return
     */
    public static Activity getTopActivity() {
        return activity;
    }

    public static Context getContext() {
        return context;
    }

    @Override
    public void onCreate() {

        super.onCreate();
        context = this;

        // ARouter 路由初始化
        if (BuildConfig.DEBUG) {
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);

        // ********************************* 获取顶部Activity start*********************************
        registerActivityLifecycleCallbacks(new AbstractActivityLifeCircleCallBack() {
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                App.activity = activity;
            }

            @Override
            public void onActivityResumed(Activity activity) {
                App.activity = activity;
            }
        });
        // ********************************* 获取顶部Activity start*********************************

        loadModuleApp();

        // app 处于前台后台
        listenForForeground();
        listenForScreenTurningOff();
    }

    public void loadModuleApp() {
        for (String s : MainPageConfig.MODULE_APP) {
            try {
                Class clazz = Class.forName(s);
                Object imp = clazz.newInstance();
                if (imp instanceof ModuleImpl) {
                    Method m = clazz.getDeclaredMethod("onLoad", Application.class);
                    m.invoke(imp, this);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

    // **************************** App 前后台切换 start ******************************

    private boolean isBackground;

    private void listenForForeground() {
        registerActivityLifecycleCallbacks(new AbstractActivityLifeCircleCallBack() {

            @Override
            public void onActivityResumed(Activity activity) {
                if (isBackground) {
                    isBackground = false;
                    notifyForeground();
                }
            }
        });
    }

    private void listenForScreenTurningOff() {
        IntentFilter screenStateFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                isBackground = true;
                notifyBackground();
            }
        }, screenStateFilter);
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        if (level == TRIM_MEMORY_UI_HIDDEN) {
            isBackground = true;
            notifyBackground();
        }
    }

    private void notifyForeground() {
        Log.e("ycwang", "Foreground----isBackground" + isBackground);
        // This is where you can notify listeners, handle session tracking, etc
    }

    private void notifyBackground() {
        Log.e("ycwang", "Background----isBackground" + isBackground);
        // This is where you can notify listeners, handle session tracking, etc
    }

    public boolean isBackground() {
        return isBackground;
    }

    // **************************** App 前后台切换 start ******************************

}
<file_sep>package com.ycwang.moduleapp.annotation;

import android.app.Activity;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author ycwang.
 * @date 2018-8-20.
 */
public class Utils {
    public static void injectView(Activity activity) {
        if (null == activity) {
            return;
        }

        Class<? extends Activity> activityClass = activity.getClass();
        Field[] fields = activityClass.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(InjectView.class)) {
                InjectView annotation = field.getAnnotation(InjectView.class);
                int value = annotation.value();
                try {
                    Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                    findViewByIdMethod.setAccessible(true);
                    findViewByIdMethod.invoke(activity, value);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }
        }
    }

}
<file_sep>package com.ycwang.moduleapp.retrofit;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * @author ycwang.
 * @date 2018-8-28.
 */
public class RetrofitManager {

    public static Retrofit getRetrofit() {
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("https://api.github.com/")
                .build();
        return retrofit;
    }
}
<file_sep>package com.ycwang.mylib1.webUrl;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.alibaba.android.arouter.facade.Postcard;
import com.alibaba.android.arouter.facade.callback.NavCallback;
import com.alibaba.android.arouter.launcher.ARouter;
import com.ycwang.mylib1.R;

/**
 * @author ycwang.
 * @date 2018-8-20.
 */
// URL 中间跳转页
public class URLReceiveActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_url_receive);

        Uri url = getIntent().getData();
        ARouter.getInstance().build(url).navigation(this, new NavCallback() {
            @Override
            public void onArrival(Postcard postcard) {
                finish();
            }
        });

    }
}
<file_sep>include ':app', ':mylib1', ':baselib', ':mylib2'
<file_sep>package com.ycwang.moduleapp.test;

import android.os.Environment;
import android.util.Log;

import java.io.File;

/**
 * @author ycwang.
 * @date 2018-9-21.
 */
public class ExternalStorage {

    /**
     * Checks if external storage is available for read and write
     *
     * @return true:available
     */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /**
     * Checks if external storage is available to at least read
     *
     * @return true:available
     */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    /**
     * creates a directory for a new photo album in the public pictures directory
     *
     * @param albumName
     * @return
     */
    public File getAlbumStorageDir(String albumName) {
        // Get the directory for the user's public pictures directory.
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e("TAG", "Directory not created");
        }
        return file;
    }

}
<file_sep>package com.ycwang.mylib1.global;

import android.app.Application;
import android.util.Log;

import com.ycwang.moduleApp.ModuleImpl;

/**
 *  初始化 用发如Application
 * @author ycwang.
 * @date 2018-8-21.
 */
public class Lib1ModuleApp implements ModuleImpl {
    @Override
    public void onLoad(Application application) {
        for (int i = 0; i < 5; i++) {
            Log.e("ycwang", "Lib1ModuleApp" + i);
        }
    }
}
<file_sep>package com.ycwang.moduleapp.global;

import android.support.v4.app.Fragment;

/**
 * @author ycwang.
 * @date 2018-8-21.
 */
public class MainPageConfig {

    /**
     * Module Application 模块化application
     */
    private static final String MODULE_LIB1_APP = "com.ycwang.mylib1.global.Lib1ModuleApp";
    private static final String MODULE_LIB2_APP = "com.ycwang.mylib2.global.Lib2ModuleApp";
    public static final String[] MODULE_APP = new String[]{MODULE_LIB1_APP, MODULE_LIB2_APP};

    /**
     * Fragment
     */
    public static String[] FRAGMENT_NAME = new String[]
            {"HomeFragment", "DiscoveryFragment", "MyFragment", "OilFragment"};
    public static String PATH = "com.ycwang.moduleapp.fragment.";
    public static Fragment[] FRAGMENTS = new Fragment[4];

    /**
     * Activity 事件分发
     */
    public static final String MODULE_PAGE_NAME =
            "com.ycwang.moduleapp.dispatchEvent.activity.view.widget.ModulePageName";
    public static final String MODULE_PAGE_BODY =
            "com.ycwang.moduleapp.dispatchEvent.activity.view.widget.ModulePageBody";

}
<file_sep>package com.ycwang.moduleapp.test;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.ImageView;

import com.ycwang.moduleapp.R;

/**
 * @author ycwang.
 * @date 2018-9-10.
 */
public class BitmapActivty extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_bitmap);

        ImageView d = findViewById(R.id.img);

        d.setImageBitmap(createTextBitmap("  ",300,46));
    }

    private  Bitmap createTextBitmap(String text, int width, int height) {
        height = (int) (getResources().getDisplayMetrics().density*height);
        width = (int) (getResources().getDisplayMetrics().density*width);
        Bitmap textBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(textBitmap);
        Paint textPaint = new Paint();
        textPaint.setColor(Color.RED);
        canvas.drawRect(0, 0, width, height, textPaint);

        // 重新设置画笔属性，绘制文字
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLACK);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setTextSize(32);
        textPaint.setFakeBoldText(true);

        text = "1120 9955 8777 5544 965";
        // 左右缩进各60
        int textWidth = (width - 120) / (text.length());
        char[] textArr = text.toCharArray();

        int y = height / 2;
        for (int i = 0; i < textArr.length; i++) {
            int x = textWidth * i + textWidth / 2 + 60;
            canvas.drawText(String.valueOf(textArr[i]), x, y, textPaint);
        }
        canvas.save();
        canvas.restore();
        return textBitmap;
    }
}
<file_sep>package com.ycwang.moduleapp.dispatchEvent.activity.baseManager;

import android.os.Bundle;

/**
 * @author ycwang.
 * @date 2018-8-21.
 */
public class CWBaseModule extends CWAbsModule {

    public CWModuleContext moduleContext;

    @Override
    public void init(CWModuleContext moduleContext, String extend) {

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onOrientationChanges(boolean isLandScape) {

    }

    @Override
    public void onDestroy() {

    }
}
<file_sep>package com.ycwang.moduleapp.retrofit;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * @author ycwang.
 * @date 2018-8-28.
 */
public class RetrofitActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        IUserBiz iUserBiz = RetrofitManager.getRetrofit().create(IUserBiz.class);
        Call<List<MyUser>> call = iUserBiz.getUsers();
        call.enqueue(new Callback<List<MyUser>>() {
            @Override
            public void onResponse(Call<List<MyUser>> call, Response<List<MyUser>> response) {

            }

            @Override
            public void onFailure(Call<List<MyUser>> call, Throwable t) {

            }
        });
    }
}
<file_sep>apply plugin: 'com.android.library'
apply plugin: 'org.greenrobot.greendao'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/module/java']
            res.srcDirs = ['src/main/res', 'src/main/module/res']
            manifest.srcFile 'src/main/AndroidManifest.xml'

        }
    }

}

greendao {
    schemaVersion 1
    daoPackage 'com.ycwang.greendao.gen'
    targetGenDir 'src/main/java'
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    api 'com.android.support:appcompat-v7:28.0.0-rc01'
    api 'com.alibaba:arouter-api:1.4.0'
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.0'
    api 'com.google.code.gson:gson:2.8.5'
    api 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    api 'org.greenrobot:greendao-api:3.2.2'
    api 'org.greenrobot:greendao:3.2.2'
    api 'com.yanzhenjie:permission:2.0.0-rc11'
    api 'android.arch.persistence.room:runtime:1.0.0'
    annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
    api 'com.squareup.retrofit2:retrofit:2.4.0'
    api 'com.squareup.retrofit2:converter-gson:2.4.0'

    api 'pub.devrel:easypermissions:1.3.0'
}
<file_sep>package com.ycwang.baselib;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Transient;
import org.greenrobot.greendao.annotation.Generated;

/**
 * @author ycwang.
 * @date 2018-8-15.
 */

@Entity(nameInDb = "mei_zi")
public class Meizi {

    @Id(autoincrement = true)
    public long _id;

    public String source;

    @NotNull
    public String url;

    @Transient
    public String add;

    @Generated(hash = 1438151240)
    public Meizi(long _id, String source, @NotNull String url) {
        this._id = _id;
        this.source = source;
        this.url = url;
    }

    @Generated(hash = 507723578)
    public Meizi() {
    }

    public long get_id() {
        return this._id;
    }

    public void set_id(long _id) {
        this._id = _id;
    }

    public String getSource() {
        return this.source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getUrl() {
        return this.url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}
<file_sep>//package com.ycwang.moduleapp.service;
//
//import android.app.Notification;
//import android.app.NotificationManager;
//import android.app.PendingIntent;
//import android.app.Service;
//import android.content.Intent;
//import android.os.Binder;
//import android.os.IBinder;
//import android.support.annotation.Nullable;
//import android.util.Log;
//
//import com.ycwang.moduleapp.R;
//
//import java.lang.reflect.InvocationTargetException;
//import java.lang.reflect.Method;
//import java.util.regex.Matcher;
//import java.util.regex.Pattern;
//
//import static java.net.Proxy.Type.HTTP;
//
///**
// * @author ycwang.
// * @date 2018-8-24.
// */
//public class ForegroundService extends Service {
//
////    private static final Class[] mStartForegroundSignature = new Class[]{int.class, Notification.class};
////
////    private static final Class[] mStopForegroundSignature = new Class[]{boolean.class};
////
////    private NotificationManager notificationManager;
////
////    private Method mStopForeground;
////
////    private Method mStartForeground;
////
////    private Object[] mStartForegroundArgs = new Object[2];
////
////    private Object[] mStopForegroundArgs = new Object[1];
////
////    @Nullable
////    @Override
////    public IBinder onBind(Intent intent) {
////        return null;
////    }
////
////    @Override
////    public void onCreate() {
////        super.onCreate();
////        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
////        try {
////            mStartForeground = ForegroundService.class.getDeclaredMethod("startForeground", mStartForegroundSignature);
////            mStopForeground = ForegroundService.class.getDeclaredMethod("stopForeground", mStopForegroundSignature);
////        } catch (NoSuchMethodException e) {
////            mStartForeground = mStopForeground = null;
////        }
////
////        Notification notification = new Notification(R.mipmap.ic_launcher,
////                "Foreground Service Started.",
////                System.currentTimeMillis());
////        PendingIntent pendingIntent = PendingIntent.getActivity(
////                this,
////                0,
////                new Intent(this, ServiceActivity.class),
////                0);
////
////        // 注意使用  startForeground ，id 为 0 将不会显示 notification
////        startForegroundCompat(1, notification);
////    }
////
////
////    @Override
////    public void onDestroy() {
////        super.onDestroy();
////        stopForegroundCompat(1);
////    }
////
////    // 以兼容性方式开始前台服务
////    private void startForegroundCompat(int id, Notification n){
////        if(mStartForeground != null){
////            mStartForegroundArgs[0] = id;
////            mStartForegroundArgs[1] = n;
////
////            try {
////                mStartForeground.invoke(this, mStartForegroundArgs);
////            } catch (IllegalArgumentException e) {
////                e.printStackTrace();
////            } catch (IllegalAccessException e) {
////                e.printStackTrace();
////            } catch (InvocationTargetException e) {
////                e.printStackTrace();
////            }
////
////            return;
////        }
////        notificationManager.notify(id, n);
////    }
////
////    private void stopForegroundCompat(int id){
////        if(mStopForeground != null){
////            mStopForegroundArgs[0] = Boolean.TRUE;
////
////            try {
////                mStopForeground.invoke(this, mStopForegroundArgs);
////            } catch (IllegalArgumentException e) {
////                e.printStackTrace();
////            } catch (IllegalAccessException e) {
////                e.printStackTrace();
////            } catch (InvocationTargetException e) {
////                e.printStackTrace();
////            }
////            return;
////        }
////
////        notificationManager.cancel(id);
////    }
//
//
//    private final static String TAG = "MyService";
//    private NotificationManager notificationMgr;
//    private boolean canRun = true;
//    private String retString = null;
//    //用于和外界交互
//    private final IBinder binder = new MyBinder();
//
//    public class MyBinder extends Binder {
//        ForegroundService getService() {
//            return ForegroundService.this;
//        }
//    }
//
//    @Override
//    public void onCreate() {
//        Thread thr = new Thread(null, new ServiceWorker(), "BackgroundSercie");
//        thr.start();
//        super.onCreate();
//    }
//
//    @Override
//    public IBinder onBind(Intent intent) {
//        Log.d(TAG, String.format("on bind,intent = %s", intent.toString()));
//        notificationMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//        displayNotificationMessage("服务已启动");
//
//        return binder;
//    }
//
//    @Override
//    public int onStartCommand(Intent intent, int flags, int startId) {
//        Log.d(TAG, "start action=" + intent.getAction());
//        notificationMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//        displayNotificationMessage("服务已启动", true);
//        return super.onStartCommand(intent, flags, startId);
//    }
//
//    @Override
//    public void onDestroy() {
//        stopForeground(true);
//        canRun = false;
//        super.onDestroy();
//    }
//
//    public String getImage(String url) {
//        return "19";
//    }
//
//    public String getRetString() {
//        return retString;
//    }
//
//    //loginValidate 为service提供给外部调用的函数
//    public boolean loginValidate(String userName, String password) throws Exception {
//        String uriString = "http://www.renyugang.cn/blog/admin/admin_check.jsp";
//        boolean ret = false;
//        Log.d("scott", "enter myservice start loginvalidate");
//        try {
//            DefaultHttpClient httpClient = new DefaultHttpClient();
//            HttpResponse response;
//            HttpPost httpPost = new HttpPost(uriString);
//            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
//            nvps.add(new BasicNameValuePair("name", userName));
//            nvps.add(new BasicNameValuePair("password", <PASSWORD>));
//
//            httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
//            response = httpClient.execute(httpPost);
//            HttpEntity entity = response.getEntity();
//            retString = EntityUtils.toString(entity);
//            retString = str_Filter(retString);
//
//            if (response.getStatusLine().getStatusCode() == 200) {
//                if (retString.equals("") == false) {
//                    if (retString.startsWith("用户名") == true) {
//                        ret = false;
//                    } else {
//                        ret = true;
//                    }
//                    Log.d("retcontent", retString);
//                    Log.d("info", userName + password);
//                    Log.d("ret", "" + ret);
//                }
//
//            }
//        } catch (Exception e) {
//            throw e;
//        }
//        return ret;
//    }
//
//    public String str_Filter(String strSource) {
//        String strPattern = "(?i)(\r\n|\r|\n|\n\r)";
//        strSource.trim();
//        Pattern p = Pattern.compile(strPattern);
//        Matcher m = p.matcher(strSource);
//        if (m.find()) {
//            strSource = strSource.replaceAll("(\r\n|\r|\n|\n\r)", "");
//        }
//        return strSource;
//    }
//
//    //为服务设置图标和文字描述
//    private void displayNotificationMessage(String message, boolean isForeground) {
//        Notification notification = new Notification(R.mipmap.ic_launcher, message,
//                System.currentTimeMillis());
//        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
//                new Intent(this, ServiceActivity.class), 0);
//        notification.setLatestEventInfo(this, "My Service", message,
//                contentIntent);
//        MyService.this.startForeground(R.id.app_notification_id, notification);
//    }
//
//    private void displayNotificationMessage(String message) {
//        Notification notification = new Notification(R.mipmap.ic_launcher, message,
//                System.currentTimeMillis());
//        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
//                new Intent(this, ServiceActivity.class), 0);
//        notification.setLatestEventInfo(this, "我的通知", message,
//                contentIntent);
//        notificationMgr.notify(R.id.app_notification_id + 1, notification);
//    }
//
//    //ServiceWorker service自身的线程，用于做自己的事情，这里为了表示服务的确在运行，每2秒打印一次log信息。
//    class ServiceWorker implements Runnable {
//        int counter = 0;
//
//        @Override
//        public void run() {
//            // do background processing here.....
//            while (canRun) {
//                Log.d("scott", "" + counter);
//                counter++;
//                try {
//                    Thread.sleep(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//    }
//
//
//}
<file_sep>package com.ycwang.mylib1.main;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.alibaba.android.arouter.facade.annotation.Autowired;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
import com.ycwang.global.MainConstant;
import com.ycwang.mylib1.R;
import com.ycwang.mylib1.permissioncheck.PermissionCheckActivity;
import com.ycwang.mylib1.waiter.HelloService;

@Route(path = MainConstant.MYLIB1_MODULE_MAIN_1)
public class MyLib1Activity extends AppCompatActivity {

    @Autowired(name = MainConstant.WAITER_MODULE_SAY_HELLO)
    HelloService helloService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_lib1);

        ARouter.getInstance().inject(this);

        String url = getIntent().getStringExtra("url");
        String title = getIntent().getStringExtra("title");

        TextView tv_2 = findViewById(R.id.tv_2);
        tv_2.setText("url:" + url + "   " + "title:" + title);

        tv_2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                helloService = (SingleService) ARouter.getInstance().build("/single/service").navigation();
                helloService.sayHello("ycwang+=============");
            }
        });

        findViewById(R.id.tv_3).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                PermissionCheckActivity.launch(MyLib1Activity.this);
            }
        });
    }
}
<file_sep>package com.ycwang.moduleapp.room;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.PrimaryKey;

/**
 * @author ycwang.
 * @date 2018-8-27.
 */

@Entity(foreignKeys = @ForeignKey(
        entity = User.class,
        parentColumns = "_id",
        childColumns = "user_id"))
public class Book {

    @ColumnInfo(name = "user_id")
    public int userId;

    @ColumnInfo(name = "book_id")
    @PrimaryKey(autoGenerate = true)
    public int bookId;

    public String title;

}
<file_sep>package com.ycwang.moduleapp.dispatchEvent.activity.view.widget;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.ycwang.moduleapp.R;
import com.ycwang.moduleapp.dispatchEvent.activity.baseManager.CWBaseModule;
import com.ycwang.moduleapp.dispatchEvent.activity.baseManager.CWModuleContext;

/**
 * @author ycwang.
 * @date 2018-8-21.
 */
public class ModulePageName extends CWBaseModule {

    private Activity activity;

    private ViewGroup viewGroup;

    private View pageName;

    private TextView pageTitle;

    @Override
    public void init(CWModuleContext moduleContext, String extend) {
        super.init(moduleContext, extend);
        activity = moduleContext.getContext();
        viewGroup = moduleContext.getViewGroups().get(0);
        this.moduleContext = moduleContext;
        initView();

    }

    private void initView() {
        pageName = LayoutInflater.from(activity).inflate(R.layout.page_name, null);
        viewGroup.addView(pageName);
        pageTitle = pageName.findViewById(R.id.txw_page_name);

        pageTitle.setText("Module Page Name");
    }
}
<file_sep>apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }

        flavorDimensions "versionCode"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

//    productFlavors {
//        baidu {
//            manifestPlaceholders = [MTA_CHANNEL_VALUE: "badiu"]
//        }
//
//        _91 {
//            manifestPlaceholders = [MTA_CHANNEL_VALUE: "91"]
//            buildConfigField('String', "ENV", ENV)
//        }
//
//        wandoujia {
//            manifestPlaceholders = [MTA_CHANNEL_VALUE: "wandoujia"]
//        }
//
//        xiaomi {
//            manifestPlaceholders = [MTA_CHANNEL_VALUE: "xiaomi"]
//        }
//    }
//
//    applicationVariants.all { variant ->
//        variant.outputs.all {
//            def fileName = "Channel${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
//            outputFileName = fileName
//        }
//    }

    sourceSets {
        main {

            java.srcDirs += 'src/main/module'
            res.srcDirs += 'src/main/moduleres'

        }
    }

}

def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.0'
    annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'

    implementation project(':mylib1')
    implementation project(':mylib2')
    implementation project(':baselib')
}
<file_sep>package com.ycwang.moduleapp.room;

import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.migration.Migration;
import android.support.annotation.NonNull;

import com.ycwang.moduleapp.global.App;

/**
 * @author ycwang.
 * @date 2018-8-27.
 */
public class DataBaseManager {

    public static UserDataBase getUserDataBase() {
        return UserDatabase.DATABASE;
    }

    public static class UserDatabase {
        static final UserDataBase DATABASE = Room
                .databaseBuilder(App.getContext(), UserDataBase.class, "room.db")
                .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                .build();
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                    + "`name` TEXT, PRIMARY KEY(`id`))");
        }
    };

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE Book "
                    + " ADD COLUMN pub_year INTEGER");
        }
    };
}
<file_sep>import groovy.util.XmlSlurper  //解析 XML 时候要引入这个 groovy的 package

def getVersionNameAdvanced() {
    def xmlFile = project.file("AndroidManifest.xml")
    def rootManifest = new XmlSlurper().parse(xmlFile)
    return rootManifest['@android:versionName']
}

ext {
    // 此段花括号中代码是闭包
    // 除了 ext.xxx=value 这种定义方法外，还可以使用 ext{}这种书写方法。
    // ext{}不是 ext(Closure)对应的函数调用。但是 ext{}中的{}确实是闭包。
    getVersionNameAdvanced = this.&getVersionNameAdvanced
}
<file_sep>package com.ycwang.moduleapp.fragment.view.fragment;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.alibaba.android.arouter.facade.annotation.Route;
import com.ycwang.global.MainConstant;
import com.ycwang.moduleapp.R;

/**
 * @author ycwang.
 * @date 2018-8-14.
 */

@Route(path = MainConstant.APP_MODULE_TO_MY)
public class MyFragment extends Fragment {

    TextView txwFragment;

    View view;

    private Activity activity;

    public static Fragment getInstance() {
        return new MyFragment();
    }

    //Fragment和Activity相关联时调用。可以通过该方法获取Activity引用，还可以通过getArguments()获取参数
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (Activity) context;
    }

    //Fragment被创建时调用。
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    //创建Fragment的布局。
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_my, container, false);
        return view;
    }

    //当Activity完成onCreate()时调用。
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        txwFragment = view.findViewById(R.id.txw_fragment);
        txwFragment.setText("Fragment My");
    }

    //当Fragment可见时调用。
    @Override
    public void onStart() {
        super.onStart();
    }

    //当Fragment可见且可交互时调用。
    @Override
    public void onResume() {
        super.onResume();
    }

    //当Fragment不可交互但可见时调用。
    @Override
    public void onPause() {
        super.onPause();
    }

    //当Fragment不可见时调用。
    @Override
    public void onStop() {
        super.onStop();
    }

    //当Fragment的UI从视图结构中移除时调用。
    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    //销毁Fragment时调用。
    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    //当Fragment和Activity解除关联时调用。
    @Override
    public void onDetach() {
        super.onDetach();
    }

    //只有onCreateView()在重写时不用写super方法，其他都需要。
}
<file_sep>package com.ycwang.mvpframe;

/**
 * @author ycwang.
 * @date 2018-8-17.
 */
public interface IPresenter {

    void onCreate();

    void onDestroy();

}
<file_sep>package com.ycwang.moduleapp.test;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.support.annotation.Nullable;
import android.widget.Toast;

/**
 * @author ycwang.
 * @date 2018-9-11.
 */
public class MessageService extends Service {

    static final int MSG_SAY_HELLO = 1;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return messenger.getBinder();
    }

    @SuppressLint("HandlerLeak")
    public class IncomeHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    final Messenger messenger = new Messenger(new IncomeHandler());

}

branch_name: refs/heads/master

languages: Java Gradle

repo_language: Java

repo_name: ycwanggh/ModuleApp


text:
<file_sep>##-----------------------------makeCacheMatrix-------------------------------##
#makeCacheMatrix takes a square invertible matrix and returns a list of four
#functions for use in cacheSolve.R, which will retrieve the inverse.
#
#Output:
#     set()       Allows users to change the matrix after makeCacheMatrix.R
#                 has been executed once.
#     get()       Returns the input matrix.
#     setinv()    Caches a calculated inverse in the makeCacheMatrix
#                 environment.
#     getinv()    Retrieves inverse values from the makeCacheMatrix
#                 environment.

#Input arguments:
# x:     a square invertible matrix that will be inverted by cacheSolve.

##-----------------------------------CODE------------------------------------##

makeCacheMatrix <- function(x = matrix()) {

      ## First check whether the input matrix is square and invertible. If not,
      ## issue a warning.

      if (!identical(dim(x)[1],dim(x)[2])){
            warning("Matrix not square. cacheSolve.R requires a matrix of
                    equal dimensions")
      } else if (det(x)==0){
            warning("Matrix not invertible. Input for cacheSolve.R must be
                    non-singular.")
      }

      ## If input matrix is correct, first empty old cache and then define the four
      ## output functions.

      inv <- NULL

      set <- function(y) {          # Function 1: set()
            x <<- y                 # - saves a new matrix in the makeCacheMatrix
            # environment.
            inv <<- NULL            # - empties inverse cache.
      }
      get <- function() x           # Function 2: get()
      # Returns the input matrix as stored in the
      # makeCacheMatrix environment.

      setinv <- function(inverse) inv <<- inverse
      # Function 3: setinv()
      # Caches a new inverse value in the
      # makeCacheMatrix environment.

      getinv <- function() inv      # Function 4: getinv()
      # Returns cached inverse values.

      ## Return these functions in a list. Elements of the list carry the name of
      ## the function in that element for easy reference in cacheSolve.R.

      list(set = set, get = get,
           setinv = setinv,
           getinv = getinv)

      }

##-------------------------------cacheSolve----------------------------------##

#cacheSolve returns the inverse of a square invertible matrix by using the
#solve() function and caches the value. If the function is then executed again
#on the same matrix, it immediately returns the cached value without calling
#on solve() again.

#Input arguments:
#   x:            a list generated by executing makeCacheMatrix.R on the
#                 matrix of interest, consisting of the functions set(),
#                 get(),setinv() and getinv(). See also makeCacheMatrix.R.

#...              Additional arguments will be passed to the solve()
#                 function that calculates the inverse.

##-----------------------------------CODE------------------------------------##

cacheSolve <- function(x, ...) {

      ## First check for cached value:

      inv <- x$getinv()                   #calls on the getinv() function
      #from x.This will return the cached
      #value (if present), or NULL if no
      #value was known.

      ## If a cached value is found: inform user and return the saved value.

      if(!is.null(inv)) {
            message("getting cached data")
            return(inv)
      }

      ## If no cached value is found: retrieve matrix with get(), calculate inverse
      ## with solve(), cache value with setinv() and return the output.

      data <- x$get()
      inv <- solve(data, ...)
      x$setinv(inv)
      inv

}

repo_language: R