рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдкреНрд░рддреНрдпреЗрдХ Android рдбреЗрд╡рд▓рдкрд░ рдиреЗ RecyclerView рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реВрдЪрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд┐рдпрд╛ред рдФрд░ рдХрдИ рд▓реЛрдЧ рдпрд╣ рднреА рджреЗрдЦрдиреЗ рдореЗрдВ рдХрд╛рдордпрд╛рдм рд░рд╣реЗ рдХрд┐ рдПрдВрдбреНрд░реЙрдЗрдб рдЖрд░реНрдХрд┐рдЯреЗрдХреНрдЪрд░ рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рд╕реЗ рдкреЗрдЬрд┐рдВрдЧ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реВрдЪреА рдореЗрдВ рдкреЗрдЬреЗрд╢рди рдХреИрд╕реЗ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд░реЗрдВред
рдпрд╣ рд╕рд░рд▓ рд╣реИ: рд╕реНрдерд┐рддрд┐-рдирд┐рд░реНрдзрд╛рд░рдг рд╕реНрд░реЛрдд рд╕реЗрдЯ рдХрд░реЗрдВ, рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рд╕реЗрдЯ рдХрд░реЗрдВ, рдкреГрд╖реНрдард╛рдВрдХрд┐рдд рдмрдирд╛рдПрдВ рдФрд░ рд╣рдорд╛рд░реЗ RecyclerView рдХреЛ рдПрдбреЗрдкреНрдЯрд░ рдФрд░ рдбрд┐рдлреНрдпреВрдЯрд┐рд▓рдХрд┐рд▓рдмреИрдХ рдХреЗ рд╕рд╛рде рд╕рднреА рдХреЛ рдЦрд┐рд▓рд╛рдПрдВред
рд▓реЗрдХрд┐рди рдХреНрдпрд╛ рд╣реЛрдЧрд╛ рдЕрдЧрд░ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХрдИ рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рд╣реИрдВ? рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╣рдо рдХрдХреНрд╖ рдореЗрдВ рдХреИрд╢ рд░рдЦрдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рдФрд░ рдиреЗрдЯрд╡рд░реНрдХ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВред
рдорд╛рдорд▓рд╛ рдХрд╛рдлреА рд╣рдж рддрдХ рд╕рд╣реА рд╣реИ рдФрд░ рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рдЬреНрдпрд╛рджрд╛ рдЬрд╛рдирдХрд╛рд░реА рдирд╣реАрдВ рд╣реИред рдореИрдВ рдЗрд╕реЗ рдареАрдХ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реВрдВрдЧрд╛ рдФрд░ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ рдХрд┐ рдРрд╕реЗ рдорд╛рдорд▓реЗ рдХреЛ рдХреИрд╕реЗ рд╣рд▓ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

рдпрджрд┐ рдЖрдк рдПрдХрд▓ рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рдХреЗ рд╕рд╛рде рдкреГрд╖реНрдард╛рдВрдХрди рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕реЗ рдЕрднреА рднреА рдкрд░рд┐рдЪрд┐рдд рдирд╣реАрдВ рд╣реИрдВ, рддреЛ рдореИрдВ рдЖрдкрдХреЛ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ рдХрд┐ рд▓реЗрдЦ рдкрдврд╝рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ рдЦреБрдж рдХреЛ рдЗрд╕рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдХрд░рд╛рдПрдВред
рдкреЗрдЬрд┐рдВрдЧ рдХреЗ рдмрд┐рдирд╛ рдПрдХ рд╕рдорд╛рдзрд╛рди рдХреИрд╕рд╛ рджрд┐рдЦреЗрдЧрд╛:
- рдХреИрд╢ рддрдХ рдкрд╣реБрдВрдЪ (рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдпрд╣ рдПрдХ рдбреЗрдЯрд╛рдмреЗрд╕ рд╣реИ)
- рдпрджрд┐ рдХреИрд╢ рдЦрд╛рд▓реА рд╣реИ - рд╕рд░реНрд╡рд░ рдХреЛ рдПрдХ рдЕрдиреБрд░реЛрдз рднреЗрдЬреЗрдВ
- рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рд╣реЛрддрд╛ рд╣реИ
- рд╣рдо рдЙрдиреНрд╣реЗрдВ рдПрдХ рд╢реАрдЯ рдореЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВ
- рдХреИрд╢ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓рд┐рдЦреЗрдВ
- рдпрджрд┐ рдХреЛрдИ рдХреИрд╢ рд╣реИ - рд╕реВрдЪреА рдореЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░реЗрдВ
- рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рд╕реЗ рдирд╡реАрдирддрдо рдбреЗрдЯрд╛ рдорд┐рд▓рддрд╛ рд╣реИ
- рд╣рдо рдЙрдиреНрд╣реЗрдВ рд╕реВрдЪреА рдореЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВ list
- рдХреИрд╢ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓рд┐рдЦреЗрдВ

рдкреЗрдЬрд┐рдВрдЧ рдХреЗ рд░реВрдк рдореЗрдВ рдЗрд╕ рддрд░рд╣ рдХреА рдПрдХ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдЪреАрдЬ, рдЬреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдЬреАрд╡рди рдХреЛ рд╕рд░рд▓ рдмрдирд╛рддреА рд╣реИ, рдпрд╣рд╛рдВ рдпрд╣ рд╣рдореЗрдВ рдЬрдЯрд┐рд▓ рдмрдирд╛рддреА рд╣реИред рдЖрдЗрдП рдпрд╣ рдХрд▓реНрдкрдирд╛ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЗрдВ рдХрд┐ рдХрдИ рдбреЗрдЯрд╛ рд╕реНрд░реЛрддреЛрдВ рдХреЗ рд╕рд╛рде рдкреГрд╖реНрдард╛рдВрдХрд┐рдд рд╕реВрдЪреА рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╕рдордп рдХреНрдпрд╛ рд╕рдорд╕реНрдпрд╛рдПрдВ рд╣реЛ рд╕рдХрддреА рд╣реИрдВред
рдПрд▓реНрдЧреЛрд░рд┐рдереНрдо рд▓рдЧрднрдЧ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╣реИ:
- рдкрд╣рд▓реЗ рдкреГрд╖реНрда рдХреЗ рд▓рд┐рдП рдХреИрд╢ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ
- рдпрджрд┐ рдХреИрд╢ рдЦрд╛рд▓реА рд╣реИ, рддреЛ рд╣рдореЗрдВ рд╕рд░реНрд╡рд░ рдбреЗрдЯрд╛ рдорд┐рд▓рддрд╛ рд╣реИ, рдЗрд╕реЗ рд╕реВрдЪреА рдореЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░реЗрдВ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рд▓рд┐рдЦреЗрдВ
- рдпрджрд┐ рдХреЛрдИ рдХреИрд╢ рд╣реИ, рддреЛ рдЙрд╕реЗ рд╕реВрдЪреА рдореЗрдВ рд▓реЛрдб рдХрд░реЗрдВ
- рдпрджрд┐ рд╣рдо рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рдЕрдВрдд рдореЗрдВ рдЖрддреЗ рд╣реИрдВ, рддреЛ рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдбреЗрдЯрд╛ рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рддреЗ рд╣реИрдВ, рдЙрдиреНрд╣реЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВ
- рд╕реВрдЪреА рдореЗрдВ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд▓рд┐рдП рд▓рд┐рдЦреЗрдВ
рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреА рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдореЗрдВ рд╕реЗ, рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рд╕реВрдЪреА рдХреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдХреИрд╢ рдХреЛ рдкрд╣рд▓реЗ рд╕рд░реНрд╡реЗрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдФрд░ рдирдП рдбреЗрдЯрд╛ рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдВрдХреЗрдд рдХреИрд╢ рдХрд╛ рдЕрдВрдд рд╣реИред

Google рдиреЗ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрд╛ рдФрд░ рдПрдХ рд╕рдорд╛рдзрд╛рди рдмрдирд╛рдпрд╛ рдЬреЛ рдкреЗрдЬрд┐рдВрдЧ рдмреЙрдХреНрд╕ рдмреЙрдиреНрдб рд╕реЗ рдмрд╛рд╣рд░ рдЖрддрд╛ рд╣реИ - рдмрд╛рдЙрдВрдбреНрд░реАрдХреЙрд▓рдмреИрдХред
рдЬрдм рд╕реНрдерд╛рдиреАрдп рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд "рд╕рдорд╛рдкреНрдд" рд╣реЛрддрд╛ рд╣реИ рдФрд░ рдирдП рдбреЗрдЯрд╛ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЛ рд╕реВрдЪрд┐рдд рдХрд░рддрд╛ рд╣реИ рддреЛ рдмрд╛рдЙрдВрдбреНрд░реАрдХреЙрд▓рдмреИрдХ рд░рд┐рдкреЛрд░реНрдЯ рдХрд░рддрд╛ рд╣реИред

рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рдПрдВрдбреНрд░реЙрдЗрдб рджреЗрд╡ рд╡реЗрдмрд╕рд╛рдЗрдЯ рдореЗрдВ рджреЛ рдбреЗрдЯрд╛ рд╕реНрд░реЛрддреЛрдВ: рдиреЗрдЯрд╡рд░реНрдХ (рд░реЗрдЯреНрд░реЛрдлрд┐рдЯ 2) + рдбреЗрдЯрд╛рдмреЗрд╕ (рдХрдХреНрд╖) рдХреЗ рд╕рд╛рде рдкреГрд╖реНрдард╛рдВрдХрди рд╕реВрдЪреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд╕рд╛рде рдПрдХ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХрд╛ рд▓рд┐рдВрдХ рд╣реИред рдпрд╣ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдРрд╕реА рдкреНрд░рдгрд╛рд▓реА рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддреА рд╣реИ, рдЖрдЗрдП рдЗрд╕ рдЙрджрд╛рд╣рд░рдг рдХреЛ рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЗрдВ рдФрд░ рдЗрд╕реЗ рдереЛрдбрд╝рд╛ рд╕рд░рд▓ рдХрд░реЗрдВред
рдЪрд▓рд┐рдП рдбреЗрдЯрд╛ рд▓реЗрдпрд░ рд╕реЗ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред рджреЛ рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рдмрдирд╛рдПрдБред
рдЗрдВрдЯрд░рдлрд╝реЗрд╕ RedditApi.ktimport com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query interface RedditApi { @GET("/r/{subreddit}/hot.json") fun getTop( @Path("subreddit") subreddit: String, @Query("limit") limit: Int): Call<ListingResponse>
рдпрд╣ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ Reddit API рдФрд░ рдореЙрдбрд▓ рдХреНрд▓рд╛рд╕реЗрд╕ (ListingResponse, ListingData, RedditChildrenResponse) рдХреЗ рдЙрди рдЕрдиреБрд░реЛрдзреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддрд╛ рд╣реИ рдЬрд┐рдирдореЗрдВ API рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЛ рдзреНрд╡рд╕реНрдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред
рдФрд░ рддреБрд░рдВрдд рд░реЗрдЯреНрд░реЛрдлрд┐рдЯ рдФрд░ рд░реВрдо рдХреЗ рд▓рд┐рдП рдПрдХ рдореЙрдбрд▓ рдмрдирд╛рдПрдВ
RedditPost.kt import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import com.google.gson.annotations.SerializedName @Entity(tableName = "posts", indices = [Index(value = ["subreddit"], unique = false)]) data class RedditPost( @PrimaryKey @SerializedName("name") val name: String, @SerializedName("title") val title: String, @SerializedName("score") val score: Int, @SerializedName("author") val author: String, @SerializedName("subreddit")
RedditDb.kt рд╡рд░реНрдЧ рдЬреЛ RoomDatabase рдХреЛ рдЗрдирд╣реЗрд░рд┐рдЯ рдХрд░реЗрдЧрд╛ред
RedditDb.kt import androidx.room.Database import androidx.room.RoomDatabase import com.memebattle.pagingwithrepository.domain.model.RedditPost @Database( entities = [RedditPost::class], version = 1, exportSchema = false ) abstract class RedditDb : RoomDatabase() { abstract fun posts(): RedditPostDao }
рдпрд╛рдж рд░рдЦреЗрдВ рдХрд┐ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдХрд┐рд╕реА рдХреНрд╡реЗрд░реА рдХреЛ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рд░ рдмрд╛рд░ рд░реВрдордбреЗрдЯрд╛рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рдмрдирд╛рдирд╛ рдмрд╣реБрдд рдорд╣рдВрдЧрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдХ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдорд╛рдорд▓реЗ рдореЗрдВ, рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рдкреВрд░реЗ рдЬреАрд╡рди рдХреЗ рд▓рд┐рдП рдЗрд╕реЗ рдПрдХ рдмрд╛рд░ рдмрдирд╛рдПрдВ!
рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдкреНрд░рд╢реНрдиреЛрдВ RedditPostDao.kt рдХреЗ рд╕рд╛рде рджрд╛рдУ рд╡рд░реНрдЧ
RedditPostDao.kt import androidx.paging.DataSource import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.memebattle.pagingwithrepository.domain.model.RedditPost @Dao interface RedditPostDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(posts : List<RedditPost>) @Query("SELECT * FROM posts WHERE subreddit = :subreddit ORDER BY indexInResponse ASC") fun postsBySubreddit(subreddit : String) : DataSource.Factory<Int, RedditPost> @Query("DELETE FROM posts WHERE subreddit = :subreddit") fun deleteBySubreddit(subreddit: String) @Query("SELECT MAX(indexInResponse) + 1 FROM posts WHERE subreddit = :subreddit") fun getNextIndexInSubreddit(subreddit: String) : Int }
рдЖрдкрдиреЗ рд╢рд╛рдпрдж рджреЗрдЦрд╛ рдХрд┐ рдкреЛрд╕реНрдЯрд╕рдмреНрд░реЗрдбреНрд░реЗрдбрд┐рдЯ рдкреЛрд╕реНрдЯ рд░рд┐рдЯреНрд░реАрд╡рд▓ рд╡рд┐рдзрд┐ рд░рд┐рдЯрд░реНрди рдХрд░рддреА рд╣реИ
DataSource.Factoryред рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╣рдорд╛рд░реЗ PagedList рдХреЛ рдмрдирд╛рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИ
LivePagedListBuilder, рдкреГрд╖реНрдарднреВрдорд┐ рдереНрд░реЗрдб рдореЗрдВред рдЖрдк рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ
рд╕рдмрдХ ред
рдорд╣рд╛рди, рдбреЗрдЯрд╛ рд▓реЗрдпрд░ рддреИрдпрд╛рд░ рд╣реИред рд╣рдо рд╡реНрдпрд╛рдкрд╛рд░ рддрд░реНрдХ рдкрд░рдд рдХреА рдУрд░ рдореБрдбрд╝рддреЗ рд╣реИрдВред рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдкреИрдЯрд░реНрди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЗрд╕рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕реЗ рдЕрд▓рдЧ рдПрдХ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░рдерд╛рдЧрдд рд╣реИред рдЗрд╕рд▓рд┐рдП, рд╣рдо рдЗрдВрдЯрд░рдлрд╝реЗрд╕ RedditPostRepository.kt рдмрдирд╛рдПрдВрдЧреЗ
RedditPostRepository.kt interface RedditPostRepository { fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost> }
рдФрд░ рддреБрд░рдВрдд рд╕рд╡рд╛рд▓ - рдХрд┐рд╕ рддрд░рд╣ рдХреА рд▓рд┐рд╕реНрдЯрд┐рдВрдЧ? рдпрд╣ рд╕реВрдЪреА рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рджрд┐рдирд╛рдВрдХ рд╡рд░реНрдЧ рд╣реИред
Listing.kt import androidx.lifecycle.LiveData import androidx.paging.PagedList import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState data class Listing<T>( // the LiveData of paged lists for the UI to observe val pagedList: LiveData<PagedList<T>>, // represents the network request status to show to the user val networkState: LiveData<NetworkState>, // represents the refresh status to show to the user. Separate from networkState, this // value is importantly only when refresh is requested. val refreshState: LiveData<NetworkState>, // refreshes the whole data and fetches it from scratch. val refresh: () -> Unit, // retries any failed requests. val retry: () -> Unit)
MainRepository.kt рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдмрдирд╛рдПрдБ
MainRepository.kt import android.content.Context import androidx.annotation.MainThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import com.android.example.paging.pagingwithnetwork.reddit.api.RedditApi import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import androidx.room.Room import com.android.example.paging.pagingwithnetwork.reddit.db.RedditDb import com.android.example.paging.pagingwithnetwork.reddit.db.RedditPostDao import com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.concurrent.Executors import androidx.paging.LivePagedListBuilder import com.memebattle.pagingwithrepository.domain.repository.core.Listing import com.memebattle.pagingwithrepository.domain.repository.boundary.SubredditBoundaryCallback import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.domain.repository.core.RedditPostRepository class MainRepository(context: Context) : RedditPostRepository { private var retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://www.reddit.com/")
рдЖрдЗрдП рджреЗрдЦреЗрдВ рдХрд┐ рд╣рдорд╛рд░реЗ рднрдВрдбрд╛рд░ рдореЗрдВ рдХреНрдпрд╛ рд╣реЛрддрд╛ рд╣реИред
рд╣рдорд╛рд░реЗ рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рдФрд░ рдбреЗрдЯрд╛ рдПрдХреНрд╕реЗрд╕ рдЗрдВрдЯрд░рдлреЗрд╕ рдХреЗ рдЙрджрд╛рд╣рд░рдг рдмрдирд╛рдПрдБред рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд▓рд┐рдП:
RoomDatabase рдФрд░ Dao, рдиреЗрдЯрд╡рд░реНрдХ рдХреЗ рд▓рд┐рдП: Retrofit рдФрд░ api рдЗрдВрдЯрд░рдлрд╝реЗрд╕ред
рдЕрдЧрд▓рд╛, рд╣рдо рдЖрд╡рд╢реНрдпрдХ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рд╡рд┐рдзрд┐ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ
fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost>
рдЬреЛ рд╕реЗрдЯ рдЕрдк рдХрд░рддрд╛ рд╣реИ:
- рдПрдХ SubRedditBoundaryCallback рдмрдирд╛рдПрдБ рдЬреЛ PagedList рдХреЛ рд╡рд┐рд░рд╛рд╕рдд рдореЗрдВ рдорд┐рд▓рд╛ рд╣реИред BoundaryCallback <>
- рд╣рдо рдХрдВрд╕реНрдЯреНрд░рдХреНрдЯрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рд╕рд╛рде рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдХрд╛рдо рдХреЗ рд▓рд┐рдП рдмрд╛рдЙрдВрдбреНрд░реАрдХреЙрд▓рдмреИрдХ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдЪреАрдЬреЛрдВ рдХреЛ рдкрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВ
- рдбреЗрдЯрд╛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЛ рд╕реВрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд░рд┐рдлреНрд░реИрд╕реНрдЯрд┐рдЧрд░ рдЯреНрд░рд┐рдЧрд░ рдмрдирд╛рдПрдБ
- рдПрдХ рд╕реВрдЪреАрдмрджреНрдз рд╡рд╕реНрддреБ рдмрдирд╛рдПрдБ рдФрд░ рд╡рд╛рдкрд╕ рд▓реМрдЯрд╛рдПрдБ
рд▓рд┐рд╕реНрдЯрд┐рдВрдЧ рд╡рд╕реНрддреБ рдореЗрдВ:
- livePagedList
- networkState - рдиреЗрдЯрд╡рд░реНрдХ рд╕реНрдерд┐рддрд┐
- рд░рд┐рдЯреНрд░реА - рдХреЙрд▓рдмреИрдХ рдЯреВ рдХреЙрд▓ рд╕рд░реНрд╡рд░ рд╕реЗ рдбреЗрдЯрд╛ рдкреБрдирдГ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП
- рддрд╛рдЬрд╝рд╛ рдХрд░реЗрдВ - рдбреЗрдЯрд╛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЯреНрд░рд┐рдЧрд░
- рддрд╛рдЬрд╝рд╛ рдХрд░реЗрдВ - рдЕрджреНрдпрддрди рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреА рд╕реНрдерд┐рддрд┐
рд╣рдо рдПрдХ рд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ
private fun insertResultIntoDb(subredditName: String, body: RedditApi.ListingResponse?)
рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдиреЗрдЯрд╡рд░реНрдХ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджрд░реНрдЬ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рддрдм рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ рдЬрдм рдЖрдкрдХреЛ рд╕реВрдЪреА рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдпрд╛ рдбреЗрдЯрд╛ рдХрд╛ рдПрдХ рдирдпрд╛ рдЯреБрдХрдбрд╝рд╛ рд▓рд┐рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред
рд╣рдо рдПрдХ рд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ
private fun refresh(subredditName: String): LiveData<NetworkState>
рдбреЗрдЯрд╛ рд░рд┐рдлреНрд░реЗрд╢ рдЯреНрд░рд┐рдЧрд░ рдХреЗ рд▓рд┐рдПред рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ: рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ, рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рд╕рд╛рдл рдХрд░рддреЗ рд╣реИрдВ, рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рдирдпрд╛ рдбреЗрдЯрд╛ рд▓рд┐рдЦрддреЗ рд╣реИрдВред
рд╣рдордиреЗ рднрдВрдбрд╛рд░ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдпрд╛ред рдЕрдм SubredditBoundaryCallback рдкрд░ рдХрд░реАрдм рд╕реЗ рдирдЬрд╝рд░ рдбрд╛рд▓рддреЗ рд╣реИрдВред
SubredditBoundaryCallback.kt import androidx.paging.PagedList import androidx.annotation.MainThread import com.android.example.paging.pagingwithnetwork.reddit.api.RedditApi import com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.concurrent.Executor import com.memebattle.pagingwithrepository.domain.util.PagingRequestHelper import com.memebattle.pagingwithrepository.domain.repository.network.createStatusLiveData class SubredditBoundaryCallback( private val subredditName: String, private val webservice: RedditApi, private val handleResponse: (String, RedditApi.ListingResponse?) -> Unit, private val ioExecutor: Executor, private val networkPageSize: Int) : PagedList.BoundaryCallback<RedditPost>() { val helper = PagingRequestHelper(ioExecutor) val networkState = helper.createStatusLiveData() @MainThread override fun onZeroItemsLoaded() { helper.runIfNotRunning(PagingRequestHelper.RequestType.INITIAL) { webservice.getTop( subreddit = subredditName, limit = networkPageSize) .enqueue(createWebserviceCallback(it)) } } @MainThread override fun onItemAtEndLoaded(itemAtEnd: RedditPost) { helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { webservice.getTopAfter( subreddit = subredditName, after = itemAtEnd.name, limit = networkPageSize) .enqueue(createWebserviceCallback(it)) } } private fun insertItemsIntoDb( response: Response<RedditApi.ListingResponse>, it: PagingRequestHelper.Request.Callback) { ioExecutor.execute { handleResponse(subredditName, response.body()) it.recordSuccess() } } override fun onItemAtFrontLoaded(itemAtFront: RedditPost) {
рд╡рд░реНрдЧ рдореЗрдВ рдХрдИ рдЖрд╡рд╢реНрдпрдХ рд╡рд┐рдзрд┐рдпрд╛рдБ рд╣реИрдВ рдЬреЛ рд╕реАрдорд╛рдмрджреНрдзрддрд╛ рдХреЛ рд╡рд┐рд░рд╛рд╕рдд рдореЗрдВ рджреЗрддреА рд╣реИрдВ:
override fun onZeroItemsLoaded()
рдбреЗрдЯрд╛рдмреЗрд╕ рдЦрд╛рд▓реА рд╣реЛрдиреЗ рдкрд░ рд╡рд┐рдзрд┐ рдХреЛ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рдпрд╣рд╛рдВ рд╣рдореЗрдВ рдкрд╣рд▓реЗ рдкреГрд╖реНрда рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд░реНрд╡рд░ рд╕реЗ рдЕрдиреБрд░реЛрдз рдкреВрд░рд╛ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред
override fun onItemAtEndLoaded(itemAtEnd: RedditPost)
рдЙрд╕ рд╡рд┐рдзрд┐ рдХреЛ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрдм рдбреЗрдЯрд╛рдмреЗрд╕ рдХрд╛ "рдиреАрдЪреЗ" рдкрд╣реБрдВрдЪ рдЧрдпрд╛ рд╣реЛрддрд╛ рд╣реИ, рддреЛ рд╣рдореЗрдВ рдЕрдЧрд▓реЗ рдкреГрд╖реНрда рдХреЛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд░реНрд╡рд░ рдХреЛ рдХреНрд╡реЗрд░реА рдХрд░рдирд╛ рдкрдбрд╝рддрд╛ рд╣реИ, рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рд╕рд░реНрд╡рд░ рд╕реНрдерд╛рдиреАрдп рд╕реНрдЯреЛрд░ рдХреЗ рдкрд┐рдЫрд▓реЗ рд░рд┐рдХреЙрд░реНрдб рдХреЗ рддреБрд░рдВрдд рдмрд╛рдж рдбреЗрдЯрд╛ рдХреЛ рдЖрдЙрдЯрдкреБрдЯ рдХрд░реЗрдЧрд╛ред
override fun onItemAtFrontLoaded(itemAtFront: RedditPost)
рдЙрд╕ рд╡рд┐рдзрд┐ рдХреЛ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬрдм рд╣рдорд╛рд░реЗ рд╕реНрдЯреЛрд░ рдХреЗ рдкрд╣рд▓реЗ рддрддреНрд╡ рддрдХ "рдкреБрдирд░рд╛рд╡реГрддреНрддрд┐рдХрд░реНрддрд╛" рдкрд╣реБрдБрдЪ рдЧрдпрд╛ рд╣реИред рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЗрд╕ рдкрджреНрдзрддрд┐ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреА рдЙрдкреЗрдХреНрд╖рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдФрд░ рдЗрд╕реЗ рдЖрдЧреЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЙрд▓рдмреИрдХ рдЬреЛрдбрд╝реЗрдВ
fun createWebserviceCallback(it: PagingRequestHelper.Request.Callback) : Callback<RedditApi.ListingResponse>
рд╣рдо рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдкреНрд░рд╛рдкреНрдд рдбреЗрдЯрд╛ рдХреЛ рд░рд┐рдХреЙрд░реНрдб рдХрд░рдиреЗ рдХреА рд╡рд┐рдзрд┐ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ
insertItemsIntoDb( response: Response<RedditApi.ListingResponse>, it: PagingRequestHelper.Request.Callback)
PagingRequestHelper рд╕рд╣рд╛рдпрдХ рдХреНрдпрд╛ рд╣реИ? рдпрд╣ рдПрдХ рд╕реНрд╡рд╛рд╕реНрдереНрдпрд╡рд░реНрдзрдХ рд╡рд░реНрдЧ рд╣реИ рдЬрд┐рд╕реЗ Google рдиреЗ рд╣рдореЗрдВ рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдореЗрдВ рд░рдЦрдиреЗ рдХреА рдкреЗрд╢рдХрд╢ рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╣рдо рдЗрд╕реЗ рддрд░реНрдХрд╢рд╛рд╕реНрддреНрд░ рдкреИрдХреЗрдЬ рдореЗрдВ рдХреЙрдкреА рдХрд░рддреЗ рд╣реИрдВред
PagingRequestHelper.kt package com.memebattle.pagingwithrepository.domain.util; import java.util.Arrays; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.AnyThread; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.paging.DataSource;
PagingRequestHelperExt.kt import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.memebattle.pagingwithrepository.domain.util.PagingRequestHelper private fun getErrorMessage(report: PagingRequestHelper.StatusReport): String { return PagingRequestHelper.RequestType.values().mapNotNull { report.getErrorFor(it)?.message }.first() } fun PagingRequestHelper.createStatusLiveData(): LiveData<NetworkState> { val liveData = MutableLiveData<NetworkState>() addListener { report -> when { report.hasRunning() -> liveData.postValue(NetworkState.LOADING) report.hasError() -> liveData.postValue( NetworkState.error(getErrorMessage(report))) else -> liveData.postValue(NetworkState.LOADED) } } return liveData }
, .
MVVM Google ViewModel LiveData.
MainActivity.kt import android.os.Bundle import android.view.KeyEvent import android.view.inputmethod.EditorInfo import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.paging.PagedList import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost import com.memebattle.pagingwithrepository.domain.repository.MainRepository import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.presentation.recycler.PostsAdapter import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { companion object { const val KEY_SUBREDDIT = "subreddit" const val DEFAULT_SUBREDDIT = "androiddev" } lateinit var model: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) model = getViewModel() initAdapter() initSwipeToRefresh() initSearch() val subreddit = savedInstanceState?.getString(KEY_SUBREDDIT) ?: DEFAULT_SUBREDDIT model.showSubReddit(subreddit) } private fun getViewModel(): MainViewModel { return ViewModelProviders.of(this, object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { val repo = MainRepository(this@MainActivity) @Suppress("UNCHECKED_CAST") return MainViewModel(repo) as T } })[MainViewModel::class.java] } private fun initAdapter() { val adapter = PostsAdapter { model.retry() } list.adapter = adapter model.posts.observe(this, Observer<PagedList<RedditPost>> { adapter.submitList(it) }) model.networkState.observe(this, Observer { adapter.setNetworkState(it) }) } private fun initSwipeToRefresh() { model.refreshState.observe(this, Observer { swipe_refresh.isRefreshing = it == NetworkState.LOADING }) swipe_refresh.setOnRefreshListener { model.refresh() } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_SUBREDDIT, model.currentSubreddit()) } private fun initSearch() { input.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_GO) { updatedSubredditFromInput() true } else { false } } input.setOnKeyListener { _, keyCode, event -> if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { updatedSubredditFromInput() true } else { false } } } private fun updatedSubredditFromInput() { input.text.trim().toString().let { if (it.isNotEmpty()) { if (model.showSubReddit(it)) { list.scrollToPosition(0) (list.adapter as? PostsAdapter)?.submitList(null) } } } } }
onCreate ViewModel, , .
LiveData ViewModel, .
MainViewModel.kt import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import com.memebattle.pagingwithrepository.domain.repository.core.RedditPostRepository class MainViewModel(private val repository: RedditPostRepository) : ViewModel() { private val subredditName = MutableLiveData<String>() private val repoResult = Transformations.map(subredditName) { repository.postsOfSubreddit(it, 10) } val posts = Transformations.switchMap(repoResult) { it.pagedList }!! val networkState = Transformations.switchMap(repoResult) { it.networkState }!! val refreshState = Transformations.switchMap(repoResult) { it.refreshState }!! fun refresh() { repoResult.value?.refresh?.invoke() } fun showSubReddit(subreddit: String): Boolean { if (subredditName.value == subreddit) { return false } subredditName.value = subreddit return true } fun retry() { val listing = repoResult?.value listing?.retry?.invoke() } fun currentSubreddit(): String? = subredditName.value }
, : retry refesh.
PagedListAdapter. .
PostAdapter.kt import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.presentation.recycler.viewholder.NetworkStateItemViewHolder import com.memebattle.pagingwithrepository.presentation.recycler.viewholder.RedditPostViewHolder class PostsAdapter( private val retryCallback: () -> Unit) : PagedListAdapter<RedditPost, RecyclerView.ViewHolder>(POST_COMPARATOR) { private var networkState: NetworkState? = null override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (getItemViewType(position)) { R.layout.reddit_post_item -> (holder as RedditPostViewHolder).bind(getItem(position)) R.layout.network_state_item -> (holder as NetworkStateItemViewHolder).bindTo( networkState) } } override fun onBindViewHolder( holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isNotEmpty()) { val item = getItem(position) (holder as RedditPostViewHolder).updateScore(item) } else { onBindViewHolder(holder, position) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { R.layout.reddit_post_item -> RedditPostViewHolder.create(parent) R.layout.network_state_item -> NetworkStateItemViewHolder.create(parent, retryCallback) else -> throw IllegalArgumentException("unknown view type $viewType") } } private fun hasExtraRow() = networkState != null && networkState != NetworkState.LOADED override fun getItemViewType(position: Int): Int { return if (hasExtraRow() && position == itemCount - 1) { R.layout.network_state_item } else { R.layout.reddit_post_item } } override fun getItemCount(): Int { return super.getItemCount() + if (hasExtraRow()) 1 else 0 } fun setNetworkState(newNetworkState: NetworkState?) { val previousState = this.networkState val hadExtraRow = hasExtraRow() this.networkState = newNetworkState val hasExtraRow = hasExtraRow() if (hadExtraRow != hasExtraRow) { if (hadExtraRow) { notifyItemRemoved(super.getItemCount()) } else { notifyItemInserted(super.getItemCount()) } } else if (hasExtraRow && previousState != newNetworkState) { notifyItemChanged(itemCount - 1) } } companion object { private val PAYLOAD_SCORE = Any() val POST_COMPARATOR = object : DiffUtil.ItemCallback<RedditPost>() { override fun areContentsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean = oldItem == newItem override fun areItemsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean = oldItem.name == newItem.name override fun getChangePayload(oldItem: RedditPost, newItem: RedditPost): Any? { return if (sameExceptScore(oldItem, newItem)) { PAYLOAD_SCORE } else { null } } } private fun sameExceptScore(oldItem: RedditPost, newItem: RedditPost): Boolean {
ViewHolder .
RedditPostViewHolder.kt import android.content.Intent import android.net.Uri import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost class RedditPostViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val title: TextView = view.findViewById(R.id.title) private val subtitle: TextView = view.findViewById(R.id.subtitle) private val score: TextView = view.findViewById(R.id.score) private var post : RedditPost? = null init { view.setOnClickListener { post?.url?.let { url -> val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) view.context.startActivity(intent) } } } fun bind(post: RedditPost?) { this.post = post title.text = post?.title ?: "loading" subtitle.text = itemView.context.resources.getString(R.string.post_subtitle, post?.author ?: "unknown") score.text = "${post?.score ?: 0}" } companion object { fun create(parent: ViewGroup): RedditPostViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.reddit_post_item, parent, false) return RedditPostViewHolder(view) } } fun updateScore(item: RedditPost?) { post = item score.text = "${item?.score ?: 0}" } }
NetworkStateItemViewHolder.kt import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar import android.widget.TextView import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.domain.repository.network.Status class NetworkStateItemViewHolder(view: View, private val retryCallback: () -> Unit) : RecyclerView.ViewHolder(view) { private val progressBar = view.findViewById<ProgressBar>(R.id.progress_bar) private val retry = view.findViewById<Button>(R.id.retry_button) private val errorMsg = view.findViewById<TextView>(R.id.error_msg) init { retry.setOnClickListener { retryCallback() } } fun bindTo(networkState: NetworkState?) { progressBar.visibility = toVisibility(networkState?.status == Status.RUNNING) retry.visibility = toVisibility(networkState?.status == Status.FAILED) errorMsg.visibility = toVisibility(networkState?.msg != null) errorMsg.text = networkState?.msg } companion object { fun create(parent: ViewGroup, retryCallback: () -> Unit): NetworkStateItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.network_state_item, parent, false) return NetworkStateItemViewHolder(view, retryCallback) } fun toVisibility(constraint : Boolean): Int { return if (constraint) { View.VISIBLE } else { View.GONE } } } }
, , Reddit androiddev. , .

, !
, Google.
рд╡рд╣ рд╕рдм рд╣реИред тАЬтАЭ , .
!