Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Demo/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

// Gson
implementation "com.google.code.gson:gson:2.10.1"

// OkHttp3
implementation "com.squareup.okhttp3:okhttp:4.10.0"

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes in the build.gradle file look good. The added dependencies for Gson and OkHttp3 seem appropriate and necessary for the application. There are no syntax errors or other issues in this code.

However, it's worth noting that the version numbers for Gson (2.10.1) and OkHttp3 (4.10.0) may become outdated over time. It's a good practice to periodically check for updates to these libraries and use the latest stable versions to ensure compatibility and take advantage of any bug fixes or new features.

Overall, this change seems fine and can be merged.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object IntentData {
const val MESSAGE = "message"
const val AUTH_KEY = "auth_key"
const val WEB_URL = "web_url"
const val USER_ID = "user_id"

/**
* Actions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import com.krunal.demo.uicomponents.constraintLayouts.GuidelineBarrierFragment
import com.krunal.demo.uicomponents.constraintLayouts.RelativeFragment
import com.krunal.demo.uicomponents.picker.DatePickerFragment
import com.krunal.demo.uicomponents.picker.TimePickerFragment
import com.krunal.demo.webservices.gsonokhttp.ui.fragments.UserListFragment
import com.krunal.demo.webservices.gsonokhttp.ui.fragments.UserDetailFragment
import com.krunal.demo.webservices.withoutlibrary.ui.fragments.NewsListFragment

object ComponentDetailRepository {
Expand All @@ -58,6 +60,10 @@ object ComponentDetailRepository {
"Search and Web view", SearchWebActivity::class.java
), ComponentDetail.FragmentComponent(
"News List", NewsListFragment::class.java
), ComponentDetail.FragmentComponent(
"User List", UserListFragment::class.java
), ComponentDetail.FragmentComponent(
"Register User", UserDetailFragment::class.java
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ class MainActivity : AppCompatActivity() {
installSplashScreen()
setupTheme()
setContentView(binding.root)
setupHostFragment()
}

private fun setupHostFragment() {
supportFragmentManager.commit {
replace(R.id.hostFragmentContainer, HostFragment::class.java, null)
}
}

private fun setupTheme() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

Here are my comments on the code changes:

  1. In the MainActivity class, a new private function setupHostFragment() has been added. This function is responsible for setting up the host fragment in the activity.

  2. Inside the setupHostFragment() function, the supportFragmentManager.commit method is used to replace the container with the HostFragment. However, it seems that the replace method is being called with incorrect arguments. The second argument should be an instance of the fragment, not the fragment class. To fix this, you can create an instance of HostFragment and pass it as the second argument to the replace method.

  3. It would be helpful to add a comment above the setupHostFragment() function explaining its purpose and what it does.

Overall, the code changes look good, but please make the necessary adjustments mentioned above.

Expand Down
15 changes: 14 additions & 1 deletion Demo/app/src/main/java/com/krunal/demo/utils/AppConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ package com.krunal.demo.utils

object AppConstants {

/**
* Log Tag
*/
const val TAG = "Demo"

/**
* News
*/
const val NEWS_API_KEY = "fa3d5d7db3f8448dac9f98e82f1108ce"
const val NEST_BASE_URL = "https://newsapi.org/v2"
const val NEWS_BASE_URL = "https://newsapi.org/v2"

/**
* User
*/
const val MOCKAPI_BASE_URL = "https://6482a62bf2e76ae1b95b5f48.mockapi.io/v1"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes look good. Here are a few suggestions:

  1. It would be helpful to update the comment for NEWS_BASE_URL to reflect the change in the base URL.

Updated code:

/**
 * News Base URL
 */
const val NEWS_BASE_URL = "https://newsapi.org/v2"

Other than that, everything looks fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.krunal.demo.webservices.gsonokhttp.data.apis

import com.google.gson.GsonBuilder
import com.google.gson.JsonParseException
import com.krunal.demo.utils.AppConstants
import com.krunal.demo.webservices.gsonokhttp.data.models.api.UserDetail
import com.krunal.demo.webservices.utils.Resource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException

object UserProfileApi {

private const val USER_URL = "${AppConstants.MOCKAPI_BASE_URL}/users"
private const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"

private val client = OkHttpClient()

suspend fun getAllUsers() = flow<Resource<List<UserDetail>>> {
emit(Resource.Loading())
val request = Request.Builder().url(USER_URL).get().build()

try {
val response = client.newCall(request).execute()
Resource.Success("")

if (!response.isSuccessful) {
emit(Resource.Error(response.message))
}

response.body?.string()?.let { body ->
val gson = GsonBuilder().setDateFormat(DATE_FORMAT).create()

val users = gson.fromJson(body, Array<UserDetail>::class.java).toList()
emit(Resource.Success(users))
}

} catch (e: JsonParseException) {
emit(Resource.Error(e.message ?: "Can't parse response"))
} catch (e: IOException) {
emit(Resource.Error(e.message ?: "Can't make request"))
}
}.flowOn(Dispatchers.IO)

suspend fun getUser(userId: Int) = flow<Resource<UserDetail>> {
val request = Request.Builder().url("$USER_URL/$userId").get().build()

try {
val response = client.newCall(request).execute()
Resource.Success("")

if (!response.isSuccessful) {
emit(Resource.Error(response.message))
}

response.body?.string()?.let { body ->
val gson = GsonBuilder().setDateFormat(DATE_FORMAT).create()

val user = gson.fromJson(body, UserDetail::class.java)
emit(Resource.Success(user))
}

} catch (e: JsonParseException) {
emit(Resource.Error(e.message ?: "Can't parse response"))
} catch (e: IOException) {
emit(Resource.Error(e.message ?: "Can't make request"))
}
}.flowOn(Dispatchers.IO)

suspend fun registerUser(userDetail: UserDetail) {
withContext(Dispatchers.IO) {
val gson = GsonBuilder().setDateFormat(DATE_FORMAT).create()
val user = gson.toJson(userDetail, UserDetail::class.java)
val body = user.toRequestBody("application/json".toMediaType())
val request = Request.Builder().url(USER_URL).post(body)
.addHeader("Content-Type", "application/json").build()

client.newCall(request).execute()
}
}

suspend fun updateUser(userDetail: UserDetail) {
withContext(Dispatchers.IO) {
val gson = GsonBuilder().setDateFormat(DATE_FORMAT).create()
val user = gson.toJson(userDetail, UserDetail::class.java)
val body = user.toRequestBody("application/json".toMediaType())
val request = Request.Builder().url("$USER_URL/${userDetail.userId}")
.put(body)
.addHeader("Content-Type", "application/json").build()

client.newCall(request).execute()
}
}

suspend fun deleteUser(userId: Int) {
withContext(Dispatchers.IO) {
val request = Request.Builder().url("$USER_URL/${userId}")
.delete()
.build()

client.newCall(request).execute()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.krunal.demo.webservices.gsonokhttp.data.models.api

data class Movie(
val name: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.krunal.demo.webservices.gsonokhttp.data.models.api

import com.google.gson.annotations.SerializedName
import java.util.Date

data class UserDetail(
@SerializedName("id")
val userId: Int? = null,
val name: String? = null,
@SerializedName("avatar")
val avatarUrl: String? = null,
val email: String? = null,
val dob: Date? = null,
val favoriteMovies: List<Movie> = emptyList()
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes look good. There are no syntax errors or logic errors in the code.

However, I noticed that there is no newline at the end of the file. It's a good practice to have a newline at the end of each file. Please add a newline at the end of the file.

Other than that, the code looks fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.krunal.demo.webservices.gsonokhttp.data.models.local

import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.krunal.demo.BR
import java.util.Date

data class UserRegistration(
var userId: Int? = null,
private var _name: String? = null,
private var _email: String? = null,
private var _dob: Date? = null,
private var _movies: String? = null
) : BaseObservable() {

@get:Bindable
var name: String? = _name
set(value) {
_name = value
field = value
notifyPropertyChanged(BR.name)
}

@get:Bindable
var email: String? = _email
set(value) {
_email = value
field = value
notifyPropertyChanged(BR.email)
}

@get:Bindable
var dob: Date? = _dob
set(value) {
_dob = value
field = value
notifyPropertyChanged(BR.dob)
}

@get:Bindable
var movies: String? = _movies
set(value) {
_movies = value
field = value
notifyPropertyChanged(BR.movies)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes look good. Here are a few suggestions:

  1. It's generally a good practice to add comments to explain the purpose and usage of the class and its properties. This can help other developers understand the code more easily.

  2. Consider adding validation logic to the setters of the name, email, dob, and movies properties. For example, you can check if the values are not null or empty before assigning them to the private variables.

  3. It's recommended to add some unit tests for this class to ensure that the binding and property change notifications are working as expected.

Other than these suggestions, the code looks fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.krunal.demo.webservices.gsonokhttp.repositories

import com.krunal.demo.webservices.gsonokhttp.data.apis.UserProfileApi
import com.krunal.demo.webservices.gsonokhttp.data.models.api.UserDetail
import com.krunal.demo.webservices.utils.Resource
import kotlinx.coroutines.flow.Flow

object UserRepository {

suspend fun getAllUsers(): Flow<Resource<List<UserDetail>>> {
return UserProfileApi.getAllUsers()
}

suspend fun getUser(userId: Int): Flow<Resource<UserDetail>> {
return UserProfileApi.getUser(userId)
}

suspend fun registerUser(userDetail: UserDetail) {
UserProfileApi.registerUser(userDetail)
}

suspend fun updateUser(userDetail: UserDetail) {
UserProfileApi.updateUser(userDetail)
}

suspend fun deleteUser(userId: Int) {
UserProfileApi.deleteUser(userId)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes in the UserRepository.kt file look good. It defines a UserRepository object that provides functions to interact with the UserProfileApi for various user operations.

Here are a few suggestions:

  1. Consider adding documentation comments (KDoc) to describe the purpose and behavior of each function. This will make it easier for other developers to understand and use the repository.

  2. It's a good practice to handle exceptions that may occur during API calls. You can wrap the API calls in a try-catch block and return an appropriate Resource object indicating the error.

  3. Ensure that the UserProfileApi functions are also handling errors properly and returning the appropriate Resource objects.

  4. Consider adding unit tests for the repository functions to ensure their correctness and to catch any potential issues.

Other than these suggestions, the code looks fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.krunal.demo.webservices.gsonokhttp.ui.adapters

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.krunal.demo.databinding.ItemUserDetailBinding
import com.krunal.demo.webservices.gsonokhttp.data.models.api.UserDetail

class UserAdapter(private val onClick: (userDetail: UserDetail) -> Unit) : RecyclerView.Adapter<UserAdapter.UserDetailHolder>() {

private val newsList: MutableList<UserDetail> = mutableListOf()

class UserDetailHolder(
private val binding: ItemUserDetailBinding,
val onClick: (UserDetail) -> Unit
) : RecyclerView.ViewHolder(binding.root) {

fun bind(userDetail: UserDetail) {
binding.userDetail = userDetail
binding.root.setOnClickListener {
onClick(userDetail)
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserDetailHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserDetailBinding.inflate(layoutInflater, parent, false)
return UserDetailHolder(binding, onClick)
}

override fun getItemCount(): Int = newsList.count()

override fun onBindViewHolder(holder: UserDetailHolder, position: Int) {
holder.bind(newsList[position])
}

@SuppressLint("NotifyDataSetChanged")
fun submitList(list: List<UserDetail>) {
newsList.clear()
newsList.addAll(list)
notifyDataSetChanged()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.krunal.demo.webservices.gsonokhttp.ui.dialogs

import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.krunal.demo.R

class DeleteConfirmationDialog(private val onConfirm: () -> Unit) : DialogFragment() {

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
AlertDialog.Builder(requireContext())
.setMessage(getString(R.string.delete_confirmation))
.setCancelable(true)
.setPositiveButton(getString(R.string.ok)) { _, _ -> onConfirm() }
.create()

companion object {
const val TAG = "DeleteConfirmationDialog"
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AIReviewRadar

The code changes in the DeleteConfirmationDialog.kt file look good. There are no syntax errors or logic errors.

However, I noticed a couple of potential improvements:

  1. In the onCreateDialog method, instead of using requireContext() to get the context, you can use context property directly from the DialogFragment class.

  2. It's a good practice to handle the negative button click as well, even if it doesn't have any action. You can add an empty lambda for the negative button click listener.

Here's the updated code with these improvements:

@@ -9,7 +9,7 @@ import androidx.fragment.app.DialogFragment
 import com.krunal.demo.R
 
 class DeleteConfirmationDialog(private val onConfirm: () -> Unit) : DialogFragment() {
 
-    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
         return AlertDialog.Builder(context)
             .setMessage(getString(R.string.delete_confirmation))
             .setCancelable(true)
@@ -17,6 +17,7 @@ class DeleteConfirmationDialog(private val onConfirm: () -> Unit) : DialogFragme
             .setPositiveButton(getString(R.string.ok)) { _, _ -> onConfirm() }
+            .setNegativeButton(getString(R.string.cancel)) { _, _ -> }
             .create()
     }
 

Other than that, the code looks good and is ready for review.

Loading