<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: LiHan</title>
    <description>The latest articles on Forem by LiHan (@encorex32268).</description>
    <link>https://forem.com/encorex32268</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1099998%2F9acf31e7-c8a6-4c4b-ba05-b680a01f89bb.jpeg</url>
      <title>Forem: LiHan</title>
      <link>https://forem.com/encorex32268</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/encorex32268"/>
    <language>en</language>
    <item>
      <title>[Android]Create AnyView Background</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 15 Aug 2023 01:07:19 +0000</pubDate>
      <link>https://forem.com/encorex32268/androidcreate-anyview-background-38n4</link>
      <guid>https://forem.com/encorex32268/androidcreate-anyview-background-38n4</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;object ViewUtils {
    fun generateBackgroundWithShadow(
        view: View, @ColorRes backgroundColor: Int,
        @DimenRes cornerRadius: Int,
        @ColorRes shadowColor: Int,
        @DimenRes elevation: Int,
        shadowGravity: Int
    ): Drawable {
        val cornerRadiusValue = view.context.resources.getDimension(cornerRadius)
        val elevationValue = view.context.resources.getDimension(elevation).toInt()
        val shadowColorValue = ContextCompat.getColor(view.context, shadowColor)
        val backgroundColorValue = ContextCompat.getColor(view.context, backgroundColor)
        val outerRadius = floatArrayOf(
            cornerRadiusValue, cornerRadiusValue, cornerRadiusValue,
            cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue,
            cornerRadiusValue
        )
        val backgroundPaint = Paint()
        backgroundPaint.style = Paint.Style.FILL
        backgroundPaint.setShadowLayer(cornerRadiusValue, 0f, 0f, 0)
        val shapeDrawablePadding = Rect()
        shapeDrawablePadding.left = elevationValue
        shapeDrawablePadding.right = elevationValue
        val DY: Int
        when (shadowGravity) {
            Gravity.CENTER -&amp;gt; {
                shapeDrawablePadding.top = elevationValue
                shapeDrawablePadding.bottom = elevationValue
                DY = 0
            }

            Gravity.TOP -&amp;gt; {
                shapeDrawablePadding.top = elevationValue * 2
                shapeDrawablePadding.bottom = elevationValue
                DY = -1 * elevationValue / 3
            }

            Gravity.BOTTOM -&amp;gt; {
                shapeDrawablePadding.top = elevationValue
                shapeDrawablePadding.bottom = elevationValue * 2
                DY = elevationValue / 3
            }

            else -&amp;gt; {
                shapeDrawablePadding.top = elevationValue
                shapeDrawablePadding.bottom = elevationValue * 2
                DY = elevationValue / 3
            }
        }
        val shapeDrawable = ShapeDrawable()
        shapeDrawable.setPadding(shapeDrawablePadding)
        shapeDrawable.paint.color = backgroundColorValue
        shapeDrawable.paint.setShadowLayer(
            cornerRadiusValue / 3,
            0f,
            DY.toFloat(),
            shadowColorValue
        )
        view.setLayerType(LAYER_TYPE_SOFTWARE, shapeDrawable.paint)
        shapeDrawable.shape = RoundRectShape(outerRadius, null, null)
        val drawable = LayerDrawable(arrayOf&amp;lt;Drawable&amp;gt;(shapeDrawable))
        drawable.setLayerInset(
            0,
            elevationValue,
            elevationValue * 2,
            elevationValue,
            elevationValue * 2
        )
        return drawable
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://medium.com/@ArmanSo/take-control-of-views-shadow-android-c6b35ba573e9"&gt;https://medium.com/@ArmanSo/take-control-of-views-shadow-android-c6b35ba573e9&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>[ktor] Create Fun To Get Food - Part 2</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Sun, 30 Jul 2023 12:18:40 +0000</pubDate>
      <link>https://forem.com/encorex32268/ktor-create-fun-to-get-food-part-2-21p3</link>
      <guid>https://forem.com/encorex32268/ktor-create-fun-to-get-food-part-2-21p3</guid>
      <description>&lt;h2&gt;
  
  
  Create RouteFood
&lt;/h2&gt;

&lt;p&gt;when you call &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://127.0.0.1:8080/food"&gt;http://127.0.0.1:8080/food&lt;/a&gt; you can get random food&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://127.0.0.1:8080/food/1"&gt;http://127.0.0.1:8080/food/1&lt;/a&gt; you can get foodlist 2&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun Route.getFood(){
    get("/food/{index}"){
        try {
            val index = call.parameters["index"]?.toInt()
            index?.let {
                val result = Food.foodList[index]
                call.respond(
                    HttpStatusCode.OK,
                    result
                )
            }
        }catch (e : IndexOutOfBoundsException){
            call.respond(
                HttpStatusCode.ExpectationFailed,
                "Error:IndexOutOfBoundsException"
            )
        }catch (e : NumberFormatException){
            call.respond(
                HttpStatusCode.ExpectationFailed,
                "Error:NumberFormatException"
            )
        }
    }
}

fun Route.randomFood(){
    get("/food"){
        call.respond(
            HttpStatusCode.OK,
            Food.foodList.random()
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Important !!! Is Add this function to ConfigureRouting
&lt;/h2&gt;

&lt;p&gt;fun Application.configureRouting() {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routing {
    getFood()
    randomFood()
    get("/") {
        call.respondText("Hello World!")
    }
    // Static plugin. Try to access `/static/index.html`
    static("/static") {
        resources("static")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;h2&gt;
  
  
  Final
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWE-ETlV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48q3hra1jh9ns4lnbcvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWE-ETlV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48q3hra1jh9ns4lnbcvi.png" alt="result-1" width="644" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u6TaZXSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvadcvvo6fdzz0eembhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u6TaZXSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvadcvvo6fdzz0eembhr.png" alt="result-2" width="660" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>[ktor] Generate Ktor Project - Part 1</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Sun, 30 Jul 2023 12:17:58 +0000</pubDate>
      <link>https://forem.com/encorex32268/ktor-generate-ktor-project-part-1-5ge</link>
      <guid>https://forem.com/encorex32268/ktor-generate-ktor-project-part-1-5ge</guid>
      <description>&lt;h2&gt;
  
  
  Ktor Project Link And Setting
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://start.ktor.io/" rel="noopener noreferrer"&gt;https://start.ktor.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyh7x8jq5md7g034w6p2h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyh7x8jq5md7g034w6p2h.png" alt="setting-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5x8o98jrkyt4sa6j88l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5x8o98jrkyt4sa6j88l1.png" alt="setting-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Download Project And Start it !
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtiu4fhy6ahvpw3z2nsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtiu4fhy6ahvpw3z2nsr.png" alt="setting-3-start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Put Food Images
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntlfnfn0ma8zn8cbzhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntlfnfn0ma8zn8cbzhl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Model - Food
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const val BASE_URL =  "http://127.0.0.1:8080/"

data class Food(
    val name : String,
    val description : String ,
    val imagePath : String
){
    companion object{
        val foodList = listOf&amp;lt;Food&amp;gt;(
            Food(
                name = "PorkRice",
                description = "basil minced pork with rice fried egg",
                imagePath = "$BASE_URL/static/food/food1.jpg"
            ),
            Food(
                name = "BBQ Chinese Food",
                description = "bbq grill cooked with hot spicy sichuan pepper sauce is chinese herb",
                imagePath = "$BASE_URL/static/food/food2.jpg"
            ),
            Food(
                name = "French fries",
                description = "crispy french fries with ketchup mayonnaise",
                imagePath = "$BASE_URL/static/food/food3.jpg"
            ),
            Food(
                name = "Vegetable",
                description = "This is Vegetable",
                imagePath = "$BASE_URL/static/food/food4.jpg"
            ),
            Food(
                name = "Noodle",
                description = "Great noodle !!",
                imagePath = "$BASE_URL/static/food/food5.jpg"
            ),
        )

    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Start Again To Test Image
&lt;/h2&gt;

&lt;p&gt;example:&lt;br&gt;
&lt;a href="http://127.0.0.1:8080/static/food/food2.jpg" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/static/food/food2.jpg&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Get Food Image File From GitHub Plz
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/encorex32268/com.example.anyprojectnamehere" rel="noopener noreferrer"&gt;https://github.com/encorex32268/com.example.anyprojectnamehere&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ktor</category>
      <category>android</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>[Android] UserListApp - UnitTest -Part6</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Thu, 15 Jun 2023 05:30:17 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-unittest-part6-4mfe</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-unittest-part6-4mfe</guid>
      <description>&lt;h2&gt;
  
  
  Add Test Dependency
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;okhttp3.4.9 is match mockwebserver4.9.3&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    //Mock Server
    testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.3'
    //okhttp
    implementation 'com.squareup.okhttp3:okhttp:3.4.9'
    //Truth
    testImplementation "com.google.truth:truth:1.1"
    testImplementation 'junit:junit:4.13.2'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create TestFile
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B1qRakx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkphr7r2dtaijuuy5nbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B1qRakx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkphr7r2dtaijuuy5nbf.png" alt="CreateTestFile" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--647vx0HO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f5cz8stvsvtzaxitv8cs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--647vx0HO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f5cz8stvsvtzaxitv8cs.png" alt="CreateTestFile2" width="800" height="884"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UserRepositoryTest
&lt;/h2&gt;

&lt;p&gt;This test is test for api send , success and fail&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserRepositoryImplTest {

    private lateinit var userRepository: UserRepository
    private lateinit var mockWebServer : MockWebServer

    @Before
    fun setUp() {
        mockWebServer = MockWebServer()
        val retrofit = Retrofit.Builder()
            .baseUrl(mockWebServer.url(""))
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val userApi = retrofit.create(UserApi::class.java)
        userRepository = UserRepositoryImpl(userApi)
    }

    @After
    fun tearDown() {
        mockWebServer.shutdown()
    }

    @Test
    fun `getRandomUserWhenSuccess`() = runBlocking{
        mockWebServer.enqueue(
            MockResponse()
                .setResponseCode(200)
                .setBody(UserDumpData.result)
        )

        var user : User?=null
        var errorMessage = ""
        userRepository.getRandomUser().collectLatest {
            user = it.data
            errorMessage = it.message?:""
        }
        assertTrue(user !=  null)
        assertTrue(errorMessage.isEmpty())
    }
    @Test
    fun `getRandomUserWhenFailed`() = runBlocking{
        mockWebServer.enqueue(
            MockResponse()
                .setResponseCode(404)
                .setBody("{}")
        )

        var user : User?=null
        var errorMessage = ""
        userRepository.getRandomUser().collectLatest {
            user = it.data
            errorMessage = it.message?:""
        }
        assertTrue(user ==  null)
        assertTrue(errorMessage.isNotEmpty())
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
    <item>
      <title>[Android] UserListApp</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 10:07:05 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-471i</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-471i</guid>
      <description>&lt;h3&gt;
  
  
  UserList App
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U8cUtIsD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8zd5rk4o4oyo0u1rx0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U8cUtIsD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8zd5rk4o4oyo0u1rx0g.png" alt="UserList App" width="603" height="1191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/encorex32268/UserListApp"&gt;https://github.com/encorex32268/UserListApp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>api</category>
      <category>retrofit</category>
    </item>
    <item>
      <title>[Android] UserListApp - UI -Part5</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 10:04:24 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-ui-part5-21lb</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-ui-part5-21lb</guid>
      <description>&lt;h2&gt;
  
  
  Create UserViewModel
&lt;/h2&gt;

&lt;p&gt;Here just one Event -&amp;gt; getDataBySize()&lt;br&gt;
Inject UserRepository We created at part4&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel(){

    var state by mutableStateOf(
        listOf&amp;lt;User&amp;gt;()
    )
    private val _uiEvent = Channel&amp;lt;UiEvent&amp;gt;()
    var uiEvent = _uiEvent.receiveAsFlow()
    init {
        getDataBySize(20)
    }
    fun getDataBySize(size : Int = 20){
        viewModelScope.launch {
            userRepository.getUserBySize(size = size.toString()).collectLatest {
                when(it){
                    is Resource.Loading-&amp;gt;{}
                    is Resource.Error-&amp;gt;{
                        Log.d("TAG", "getDataBySize: ${it.message}")
                        _uiEvent.send(UiEvent.APIError)
                    }
                    is Resource.Success-&amp;gt;{
                        it.data?.let {
                            state = it
                            Log.d("TAG", "getDataBySize: ${it}")
                        }
                    }
                }
            }
        }
    }



}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  If API Failed , We can use UiEvent send Event to Screen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sealed class UiEvent{
    object APIError : UiEvent()
    object Success : UiEvent()
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UI - UserScreen LazyColumn
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun UserScreen(
    users : List&amp;lt;User&amp;gt;
) {
    LazyColumn{
        items(users){user -&amp;gt;
            UserItem(user)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UI - UserItem
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun UserItem(
    user: User,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
    ) {
        AsyncImage(
            modifier = Modifier
                .size(80.dp),
            model = user.avatar,
            contentDescription = null,
        )
        Column(
            modifier = Modifier
                .weight(1f)
                .fillMaxWidth()

        ) {
            Row{
                Icon(
                    imageVector = ImageVector.vectorResource(
                        id = when(user.gender){
                                "Male" -&amp;gt; R.drawable.baseline_man_24
                                "Female" -&amp;gt; R.drawable.baseline_woman_24
                                else-&amp;gt;{
                                    R.drawable.baseline_question_mark_24
                                }
                            }
                    ),
                    tint = Color.Unspecified,
                    contentDescription = "gender mark"
                )
                Text(text = user.username)
            }
            Text(text = user.dateOfBirth)
            Text(text = user.email)
            Text(text = user.phoneNumber)


        }
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
    <item>
      <title>[Android] UserListApp - Dependency Injection - Part4</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 09:56:02 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-dependency-injection-part4-51kd</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-dependency-injection-part4-51kd</guid>
      <description>&lt;h2&gt;
  
  
  Create Application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@HiltAndroidApp
class UserApplication  : Application()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add Name ... on AndroidManifest
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;application
        android:name=".UserApplication"
    ....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add User Internet Permission
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;uses-permission android:name="android.permission.INTERNET"&amp;gt;&amp;lt;/uses-permission&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create UserModule.kt
&lt;/h2&gt;

&lt;p&gt;這個檔案是為了提前產生物件.比如說：UserApi , UserRepostioryImpl&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
object UserModule {

    @Provides
    @Singleton
    fun providesUserApi() : UserApi {
        return Retrofit.Builder()
           .baseUrl(UserApi.BASE_URL)
           .addConverterFactory(GsonConverterFactory.create())
           .build().create()
    }

    @Provides
    @Singleton
    fun providesUserRepository(
        userApi : UserApi
    ) : UserRepository {
        return UserRepositoryImpl(userApi)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
    <item>
      <title>[Android] UserListApp - Data Part - Part3</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 09:46:53 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-data-part-part3-540j</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-data-part-part3-540j</guid>
      <description>&lt;h1&gt;
  
  
  Remote Info
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://random-data-api.com/documentation"&gt;https://random-data-api.com/documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Response Data is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dxQgv_7t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tn22hjazvaldkn505f0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dxQgv_7t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tn22hjazvaldkn505f0x.png" alt="Data Type" width="800" height="928"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create UserDto
&lt;/h2&gt;

&lt;p&gt;Not create all data type .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class UserDto(
    val id : Int ,
    val username : String ,
    val gender : String ,
    val phone_number : String ,
    val date_of_birth : String,
    val email : String,
    val avatar : String,
    val address: Address,
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class Address(
    val city: String,
    val coordinates: Coordinates,
    val country: String,
    val state: String,
    val street_address: String,
    val street_name: String,
    val zip_code: String
)
data class Coordinates(
    val lat: Double,
    val lng: Double
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Remote API Interface UserApi
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface UserApi {

    @GET("users")
    suspend fun getRandomUser() : Response&amp;lt;UserDto&amp;gt;

    @GET("users")
    suspend fun getUserBySize(
        @Query("size") size : String,
    ) : Response&amp;lt;List&amp;lt;UserDto&amp;gt;&amp;gt;

    companion object{
        const val BASE_URL = "https://random-data-api.com/api/v2/"
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Mapper
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun UserDto.toUser() : User {
    return User(
        id= id,
        username = username,
        gender = gender,
        phoneNumber = phone_number,
        dateOfBirth = date_of_birth,
        email = email,
        avatar = avatar,
        lat = address.coordinates.lat,
        lng = address.coordinates.lng
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Custom Result Class Resource.kt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sealed class Resource&amp;lt;T&amp;gt;(val data : T?=null , val message : String ?=null){
    class Loading&amp;lt;T&amp;gt;(data: T? = null) : Resource&amp;lt;T&amp;gt;(data)
    class Error&amp;lt;T&amp;gt;(data: T? = null , message: String?) : Resource&amp;lt;T&amp;gt;(data,message)
    class Success&amp;lt;T&amp;gt;(data: T?) : Resource&amp;lt;T&amp;gt;(data)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Domain Repository UserRepository
&lt;/h2&gt;

&lt;p&gt;這個UserRepository裡面的資料一定是User，因為要在App內使用的&lt;br&gt;
所以在實作那層會有一個Mapper去做轉換(domain&amp;gt;util&amp;gt;UserMapper.kt)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface UserRepository {

    suspend fun getRandomUser(): Flow&amp;lt;Resource&amp;lt;User&amp;gt;&amp;gt;

    suspend fun getUserBySize(size : String) : Flow&amp;lt;Resource&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Data Repository UserRepositoryImpl
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserRepositoryImpl(
    private val userApi: UserApi
) : UserRepository{

    override suspend fun getRandomUser(): Flow&amp;lt;Resource&amp;lt;User&amp;gt;&amp;gt; = flow{
       emit(Resource.Loading())
        try {
            val result = userApi.getRandomUser()
            if (result.isSuccessful){
                result.body()?.let {
                    emit(
                        Resource.Success(it.toUser())
                    )
                }
            }else{
                emit(
                    Resource.Error(
                        data = null,
                        message = "${result.errorBody()?.string()}"
                    )
                )
            }
        }catch (e : IOException){
            emit(
                Resource.Error(
                    data = null,
                    message = "IOE Error"
                )
            )
        }

    }

    override suspend fun getUserBySize(size: String): Flow&amp;lt;Resource&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;&amp;gt; = flow{
        emit(
            Resource.Loading()
        )
        try {
            val result = userApi.getUserBySize(size)
            if (result.isSuccessful){
                result.body()?.let {
                    it.let {
                        emit(
                            Resource.Success(
                                data =it.map { userDto -&amp;gt;
                                    userDto.toUser()
                                }
                            )
                        )
                    }
                }
            }else{
                emit(
                    Resource.Error(
                        data = emptyList(),
                        message = result.errorBody()?.string()
                    )
                )
            }
        }catch (e : IOException){
            emit(
                Resource.Error(
                    data = null,
                    message = e.message
                )
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
    <item>
      <title>[Android] UserListApp - Folder Create - Part2</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 09:19:20 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-folder-create-356e</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-folder-create-356e</guid>
      <description>&lt;h2&gt;
  
  
  Package
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;feature_user

&lt;ul&gt;
&lt;li&gt;data

&lt;ul&gt;
&lt;li&gt;repository&lt;/li&gt;
&lt;li&gt;local

&lt;ul&gt;
&lt;li&gt;model &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;remote

&lt;ul&gt;
&lt;li&gt;model&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;domain

&lt;ul&gt;
&lt;li&gt;model&lt;/li&gt;
&lt;li&gt;repository&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;di&lt;/li&gt;
&lt;li&gt;presentation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;local -&amp;gt; Roomdatabase (Android內建的資料庫)&lt;br&gt;
model -&amp;gt; XXXEntity.class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;remote -&amp;gt; Retrofit (需要調用網路)&lt;br&gt;
model -&amp;gt; XXXDto.class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;repository -&amp;gt; “實作”獲取資料的介面Interface&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Domain&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model -&amp;gt; 主要是內部使用的class（用來顯示的類別）&lt;/li&gt;
&lt;li&gt;repository -&amp;gt; 獲取資料的介面Interface&lt;/li&gt;
&lt;li&gt;util -&amp;gt; mapper (Dto , Entity to Model)
ex: UserDto , UserEntity -&amp;gt; User&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Presentation&lt;/strong&gt;&lt;br&gt;
-&amp;gt; Screen(Compose)&lt;br&gt;
-&amp;gt; ViewModel&lt;br&gt;
-&amp;gt; Component&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
    <item>
      <title>[Android] UserListApp - Setting - Part1</title>
      <dc:creator>LiHan</dc:creator>
      <pubDate>Tue, 13 Jun 2023 05:19:27 +0000</pubDate>
      <link>https://forem.com/encorex32268/android-userlistapp-setting-part1-9if</link>
      <guid>https://forem.com/encorex32268/android-userlistapp-setting-part1-9if</guid>
      <description>&lt;p&gt;&lt;strong&gt;Plugins&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/encorex32268/UserListApp/blob/master/app/build.gradle"&gt;build.gradle(:app)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Coil Compose
    implementation "io.coil-kt:coil-compose:2.2.2"
    implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"

    // Dagger - Hilt
    implementation "com.google.dagger:hilt-android:2.45"
    kapt "com.google.dagger:hilt-android-compiler:2.45"
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation "androidx.hilt:hilt-navigation-compose:1.0.0"

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/encorex32268/UserListApp/blob/master/build.gradle"&gt;build.gradle(:project)&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;buildscript {
    ext {
        compose_ui_version = '1.4.0'
    }
    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.45"
    }
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '8.0.0' apply false
    id 'com.android.library' version '8.0.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>kotlin</category>
      <category>retrofit</category>
      <category>api</category>
    </item>
  </channel>
</rss>
