-
Notifications
You must be signed in to change notification settings - Fork 38
Homework MVVM+DI+Network #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package ru.otus.basicarchitecture | ||
|
|
||
| import android.app.Application | ||
| import dagger.hilt.android.HiltAndroidApp | ||
|
|
||
| @HiltAndroidApp | ||
| class App :Application(){ | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package ru.otus.basicarchitecture.cache | ||
|
|
||
| import java.time.LocalDate | ||
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
|
|
||
| @Singleton | ||
| class WizardCache @Inject constructor(){ | ||
|
|
||
| var person:Person?=null | ||
| var address:Address?=null | ||
| var interests:Interests?=null | ||
|
|
||
| } | ||
|
|
||
| data class Person (val name: String = "test", val surname: String = "test", val dateOfBirth: String = "01.01.1970") | ||
| data class Address(val country: String = "", val city: String = "", val address: String = "") | ||
| data class Interests(val selectedInterest: List<String> = emptyList<String>()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package ru.otus.basicarchitecture.dadata | ||
|
|
||
| import retrofit2.http.Body | ||
| import retrofit2.http.Headers | ||
| import retrofit2.http.POST | ||
|
|
||
| interface DadataApi { | ||
| @Headers("Content-Type: application/json", "Accept: application/json") | ||
| @POST("suggestions/api/4_1/rs/suggest/address") | ||
| suspend fun getAddressSuggestions(@Body request: AddressSuggestionRequest): DadataResponse | ||
| } | ||
|
|
||
| data class AddressSuggestionRequest(val query: String) | ||
|
|
||
| data class DadataResponse(val suggestions: List<AddressSuggestion>) | ||
| data class AddressSuggestion(val value: String) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package ru.otus.basicarchitecture.dadata | ||
|
|
||
| import okhttp3.Interceptor | ||
| import okhttp3.OkHttpClient | ||
| import okhttp3.logging.HttpLoggingInterceptor | ||
| import retrofit2.Retrofit | ||
| import retrofit2.converter.gson.GsonConverterFactory | ||
| import ru.otus.basicarchitecture.BuildConfig | ||
| import java.util.concurrent.TimeUnit | ||
|
|
||
| object DadataApiService { | ||
| private const val BASE_URL = "https://suggestions.dadata.ru/" | ||
| private const val API_KEY = BuildConfig.DADATA_API_KEY | ||
|
|
||
| private val client: OkHttpClient by lazy { | ||
| val loggingInterceptor = HttpLoggingInterceptor().apply { | ||
| level = HttpLoggingInterceptor.Level.BODY | ||
| } | ||
| val headerInterceptor = Interceptor { chain -> | ||
| val request = chain.request().newBuilder() | ||
| .addHeader("Authorization", "Token $API_KEY") | ||
| .build() | ||
| chain.proceed(request) | ||
| } | ||
|
|
||
| OkHttpClient.Builder() | ||
| .addInterceptor(loggingInterceptor) | ||
| .addInterceptor(headerInterceptor) | ||
| .connectTimeout(30, TimeUnit.SECONDS) | ||
| .readTimeout(30, TimeUnit.SECONDS) | ||
| .build() | ||
| } | ||
|
|
||
| val api: DadataApi by lazy { | ||
| Retrofit.Builder() | ||
| .baseUrl(BASE_URL) | ||
| .client(client) | ||
| .addConverterFactory(GsonConverterFactory.create()) | ||
| .build() | ||
| .create(DadataApi::class.java) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package ru.otus.basicarchitecture.fragments | ||
|
|
||
| import androidx.fragment.app.viewModels | ||
| import android.os.Bundle | ||
| import androidx.fragment.app.Fragment | ||
| import android.view.LayoutInflater | ||
| import android.view.View | ||
| import android.view.ViewGroup | ||
| import android.widget.ArrayAdapter | ||
| import android.widget.AutoCompleteTextView | ||
| import android.widget.Button | ||
| import android.widget.Toast | ||
| import androidx.core.widget.addTextChangedListener | ||
| import dagger.hilt.android.AndroidEntryPoint | ||
| import ru.otus.basicarchitecture.R | ||
| import ru.otus.basicarchitecture.viewmodel.AddressViewModel | ||
|
|
||
| @AndroidEntryPoint | ||
| class AddressFragment : Fragment() { | ||
|
|
||
| companion object { | ||
| fun newInstance() = AddressFragment() | ||
| } | ||
|
|
||
| private val viewModel: AddressViewModel by viewModels() | ||
|
|
||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
|
|
||
| } | ||
|
|
||
| override fun onCreateView( | ||
| inflater: LayoutInflater, container: ViewGroup?, | ||
| savedInstanceState: Bundle? | ||
| ): View { | ||
| return inflater.inflate(R.layout.fragment_address, container, false) | ||
| } | ||
|
|
||
| private lateinit var addressEditText: AutoCompleteTextView | ||
| private lateinit var nextBtn: Button | ||
|
|
||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| super.onViewCreated(view, savedInstanceState) | ||
|
|
||
| addressEditText = view.findViewById(R.id.address) | ||
| nextBtn = view.findViewById(R.id.button_to_interest) | ||
|
|
||
|
|
||
| viewModel.addressSuggestions.observe(viewLifecycleOwner) { suggestions -> | ||
| val adapter = ArrayAdapter( | ||
| requireContext(), | ||
| android.R.layout.simple_dropdown_item_1line, | ||
| suggestions | ||
| ) | ||
| addressEditText.setAdapter(adapter) | ||
| if (suggestions.isNotEmpty()) { | ||
| addressEditText.showDropDown() | ||
| } | ||
| } | ||
|
|
||
| addressEditText.addTextChangedListener { | ||
| if(it != null && it.length > 5) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RubaN0id, мне кажется, стоит сделать проверку в модели. Допустим, вы захотите протестировать, что запросы не посылаются до 5 символов - вам придется делать андроид-тесты, что геморно. Потом поменяли вьюху, и прицепили ее к новой модели. И забыли... В общем, мне кажется, такому место в модели |
||
| viewModel.updateAddress(it.toString()) | ||
| viewModel.fetchAddressSuggestions(it.toString()) | ||
| } | ||
| } | ||
|
|
||
| nextBtn.setOnClickListener { | ||
| if (nextBtn.isEnabled) { | ||
| viewModel.saveData() | ||
| parentFragmentManager.beginTransaction() | ||
| .replace(R.id.fragment_container_view_tag, InterestFragment.newInstance()) | ||
| .addToBackStack(null) | ||
| .commit() | ||
| } else { | ||
| Toast.makeText(context, "Валидация не пройдена", Toast.LENGTH_SHORT).show() | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package ru.otus.basicarchitecture.fragments | ||
|
|
||
| import androidx.fragment.app.viewModels | ||
| import android.os.Bundle | ||
| import androidx.fragment.app.Fragment | ||
| import android.view.LayoutInflater | ||
| import android.view.View | ||
| import android.view.ViewGroup | ||
| import android.widget.Button | ||
| import android.widget.TextView | ||
| import com.google.android.flexbox.FlexboxLayout | ||
| import dagger.hilt.android.AndroidEntryPoint | ||
| import ru.otus.basicarchitecture.R | ||
| import ru.otus.basicarchitecture.viewmodel.InterestViewModel | ||
| import javax.inject.Inject | ||
|
|
||
| @AndroidEntryPoint | ||
| class InterestFragment @Inject constructor(): Fragment() { | ||
|
|
||
| companion object { | ||
| fun newInstance() = InterestFragment() | ||
| } | ||
|
|
||
| private val viewModel: InterestViewModel by viewModels() | ||
|
|
||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
|
|
||
| } | ||
|
|
||
| override fun onCreateView( | ||
| inflater: LayoutInflater, container: ViewGroup?, | ||
| savedInstanceState: Bundle? | ||
| ): View { | ||
| return inflater.inflate(R.layout.fragment_interest, container, false) | ||
| } | ||
|
|
||
| private lateinit var tagFlexboxLayout: FlexboxLayout | ||
| private lateinit var nextBtn: Button | ||
|
|
||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| super.onViewCreated(view, savedInstanceState) | ||
|
|
||
| tagFlexboxLayout = view.findViewById(R.id.tagFlexboxLayout) | ||
| nextBtn = view.findViewById(R.id.button_to_summary) | ||
|
|
||
| viewModel.selectedInterests.observe(viewLifecycleOwner) { selectedInterests -> | ||
| updateTags(selectedInterests) | ||
| } | ||
|
|
||
| nextBtn.setOnClickListener { | ||
| viewModel.saveSelectedInterests() | ||
| parentFragmentManager.beginTransaction() | ||
| .replace(R.id.fragment_container_view_tag, SummaryFragment.newInstance()) | ||
| .addToBackStack(null) | ||
| .commit() | ||
| } | ||
|
|
||
| viewModel.interests.forEach { interest -> | ||
| val tagView = createTagView(interest) | ||
| tagView.setOnClickListener { | ||
| viewModel.toggleInterest(interest) | ||
| } | ||
| tagFlexboxLayout.addView(tagView) | ||
| } | ||
| } | ||
|
|
||
| private fun createTagView(interest: String): TextView { | ||
| val tagView = LayoutInflater.from(context).inflate(R.layout.tag, tagFlexboxLayout, false) as TextView | ||
| tagView.text = interest | ||
| return tagView | ||
| } | ||
|
|
||
| private fun updateTags(selectedInterests: List<String>) { | ||
| for (i in 0 until tagFlexboxLayout.childCount) { | ||
| val tagView = tagFlexboxLayout.getChildAt(i) as TextView | ||
| val interest = tagView.text.toString() | ||
| tagView.isSelected = selectedInterests.contains(interest) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RubaN0id, не уверен, что инжект фрагмента - это хорошая идея. Нужно помнить, что андроид сохраняет состояние вьюх при изменении конфигурации. В результате, у вас будет два фрагмента - один перевернутый, а второй - созданный даггером. Это можно проверить в онКриейт - посмотреть, нулл или не нулл пришел в функцию. Если не нулл, то запрос fragmentManager.findFragmentByTag вернет ненулевой результат. И это уже и есть ваш фрагмент