Android LiveData: Unable to receive updates on Observable? - android-livedata

I am using android Architecture Component LiveData to notify UI using Observable LiveData but it is not getting triggered. below is the code snippet.
AuthRepository
class AuthRepository(private val repository:Avails) {
fun auth(mobile: String): LiveData<Boolean>{
var data: MutableLiveData<Boolean> = MutableLiveData()
repository.auth(prepareHeaders(), AuthRequest(mobile))
.enqueue(object : Callback<AuthResponse>{
override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
if(response.isSuccessful){
data.value = true
}
}
override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
data.value = false
}
})
return data
}
}
LoginViewModel
class LoginViewModel(private val repository: AuthRepository) : ViewModel() {
var _showOtpScreen: MutableLiveData<Boolean> = MutableLiveData()
fun auth(mobile: String){
_showOtpScreen.value = repository.auth(mobile).value
}
}
LoginFragment
class LoginFragment : Fragment() {
private lateinit var loginViewModel: LoginViewModel
companion object {
private const val sTag: String = "LoginFragment"
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val authRepository = AuthRepository(AvailsClient.retrofit.create(Avails::class.java))
loginViewModel = ViewModelProviders.of(this,LoginViewModelFactory(authRepository)).get(LoginViewModel::class.java)
loginViewModel._showOtpScreen.observe(this, Observer {
if(it != null){
if(it){
Log.e(sTag,"OTP RECEIVED")
findNavController().navigate(R.id.action_loginFragment_to_verifyOtpFragment)
}else{
Log.e(sTag,"Failed to get OTP")
}
}
})
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnContinue.setOnClickListener {
loginViewModel.auth(edtPhoneNumber.text.toString())
}
}
}
Above code is not able to Observe _showOtpScreen it is only called once with null value and never gets called again when service call completes.

Event wrapper is the solution for above problem.

Related

How to share HttpClient between Multiplatform Ktor and Coil?

I want to use Coil image library to load images from the api with the same cookie that was set before. Therefore I want to use the same HttpClient both for my Ktor networking calls and for Image Loading with Coil.
How can I share the same HttpClient between Ktor and Coil? I assume, I need to adjust dependencies somehow, but I can't wrap my head around it.
My KtorApiImpl in shared module
class KtorApiImpl(log: Kermit) : KtorApi {
val baseUrl = BuildKonfig.baseUrl
// If this is a constructor property, then it gets captured
// inside HttpClient config and freezes this whole class.
#Suppress("CanBePrimaryConstructorProperty")
private val log = log
override val client = HttpClientProvider().getHttpClient().config {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
log.v("Network") { message }
}
}
level = LogLevel.INFO
}
}
init {
ensureNeverFrozen()
}
override fun HttpRequestBuilder.apiUrl(path: String) {
url {
takeFrom(baseUrl)
encodedPath = path
}
}
override fun HttpRequestBuilder.json() {
contentType(ContentType.Application.Json)
}
}
actual HttpClientProvider in androidMain
var cookieJar: CookieJar = object : CookieJar {
private val cookieStore: HashMap<String, List<Cookie>> = HashMap()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url.host] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val cookies = cookieStore[url.host]
return cookies ?: ArrayList()
}
}
actual class HttpClientProvider actual constructor() {
actual fun getHttpClient(): HttpClient {
return HttpClient(OkHttp) {
engine {
preconfigured = getOkHttpClient()
}
}
}
}
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(cookieJar)
.build()
}
ImageLoaderFactory in androidApp - how to use an HttpClient instead of creating new?
class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(context)
.availableMemoryPercentage(0.25) // Use 25% of the application's available memory.
.crossfade(true) // Show a short crossfade when loading images from network or disk.
.componentRegistry {
add(ByteArrayFetcher())
}
.okHttpClient {
// Create a disk cache with "unlimited" size. Don't do this in production.
// To create the an optimized Coil disk cache, use CoilUtils.createDefaultCache(context).
val cacheDirectory = File(context.filesDir, "image_cache").apply { mkdirs() }
val cache = Cache(cacheDirectory, Long.MAX_VALUE)
// Lazily create the OkHttpClient that is used for network operations.
OkHttpClient.Builder()
.cache(cache)
.build()
}
.build()
}
}
Koin dependencies in androidApp
#Suppress("unused")
class MainApp : Application() {
override fun onCreate() {
super.onCreate()
initKoin(
module {
single<Context> { this#MainApp }
single<AppInfo> { AndroidAppInfo }
single { CoilImageLoaderFactory(get<Context>())}
single<SharedPreferences> {
get<Context>().getSharedPreferences("MAIN_SETTINGS", Context.MODE_PRIVATE)
}
single {
{ Log.i("Startup", "Hello from Android/Kotlin!") }
}
}
)
}
}
And then Main Activity
class MainActivity : AppCompatActivity() {
val loaderFactory: CoilImageLoaderFactory by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalImageLoader provides loaderFactory.newImageLoader()) {
MainTheme {
ProvideWindowInsets {
Surface {
MainScreen()
}
}
}
}
}
}
}
I accessed the OkHttpClient from ImageLoader with
class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory, KoinComponent {
val ktorApiImpl: KtorApi by inject()
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(context)
.componentRegistry {
add(ByteArrayFetcher())
}
.okHttpClient {
val config = ktorApiImpl.client.engine.config as OkHttpConfig
config.preconfigured as OkHttpClient
}
.build()
}
You could use ImageLoader.Builder.callFactory{} to provide your own Call.Factory used for network requests. The downside is you would have to map whatever type your KtorApiImpl returns to okttp3.Response which Coil understands.
Here’s a sample that describes how to implement the Call.Factory interface and provide it to Coil’s ImageLoader
ImageLoader.Builder(context)
.callFactory {
Call.Factory {
object: Call {
private var job: Job? = null
override fun clone(): Call {
TODO(“Not yet implemented”)
}
override fun request(): Request {
return it
}
override fun execute(): Response {
return runBlocking {
// Call KTOR client here
}
}
override fun enqueue(responseCallback: Callback) {
// Use a proper coroutines scope
job = GlobalScope.launch {
// Call KTOR client here
}
}
override fun cancel() {
job?.cancel()
}
override fun isExecuted(): Boolean {
return job?.isCompleted ?: false
}
override fun isCanceled(): Boolean {
return job?.isCancelled ?: false
}
override fun timeout(): Timeout {
// Your Timeout here
}
}
}
}

How to provide dependency of List Adapter class using dagger hilt? - when adapter class takes interface as a parameters

suppose this is a interface
interface Listener {
fun deleteOnClick(position:Int,userId:Int)
fun updateOnClick(position: Int,userId: Int,name:String,phoneNo:Long)
}
And this is my Adapter class, where i am passing listener interface as a parameter 👇🏻
class PhoneAdapter
#Inject
constructor(private val listener: Listener) : ListAdapter<Phone,PhoneAdapter.PhoneViewHolder>(Diff) {
inner class PhoneViewHolder(private val binding: EachRowBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(phone: Phone){
binding.apply {
name.text = phone.name
phoneNo.text = phone.phoneNo.toString()
delete.setOnClickListener {
listener.deleteOnClick(adapterPosition,phone.userId)
}
root.setOnClickListener {
listener.updateOnClick(adapterPosition,phone.userId,phone.name,phone.phoneNo)
}
}
}
}
object Diff : DiffUtil.ItemCallback<Phone>(){
override fun areItemsTheSame(oldItem: Phone, newItem: Phone): Boolean {
return oldItem.userId == newItem.userId
}
override fun areContentsTheSame(oldItem: Phone, newItem: Phone): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhoneViewHolder {
return PhoneViewHolder(EachRowBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
override fun onBindViewHolder(holder: PhoneViewHolder, position: Int) {
val phone = getItem(position)
if(phone != null){
holder.bind(phone)
}
}
}
And MainActivity implementing this interface👇🏻
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), Listener {
private val binding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
....
....
}
So my question is how to provide dependency of Listener interface..

Confused about LiveData Observe

My situation is When user enter loading fragment, check LoggedIn, true go straight to MainFragment, false jump to LoginFramgnet.
here is LoadingFragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Logger.t(LoadingFragment::class.java.simpleName).i("onCreateView")
val binding = LoadingFragmentBinding.inflate(inflater, container, false)
subscribeUi()
return binding.root
}
fun subscribeUi(){
val factory: LoadingViewModelFactory = InjectorUtils.provideLoadingViewModelFactory()
val viewModel: LoadingViewModel = ViewModelProviders.of(this, factory).get(LoadingViewModel::class.java)
Logger.t(LoadingFragment::class.java.simpleName).i("viewModel = " + viewModel.toString())
viewModel.checkLogin()
viewModel.isToLogin.observe(viewLifecycleOwner, Observer {
if (it){
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
}else{
Logger.t(LoadingFragment::class.java.simpleName).i("to start login")
findNavController().navigate(R.id.start_login)
}
})
}
here is LoadingViewModel:
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
val isToLogin: MediatorLiveData<Boolean> = MediatorLiveData()
fun checkLogin(){
isToLogin.addSource(loadingRepository.checkLogin()) {
isToLogin.value = it
}
}
}
here is the Loadingrepository:
fun checkLogin() : MutableLiveData<Boolean> {
val data: MutableLiveData<Boolean> = MutableLiveData()
api.httpGet(SDBUrl.CHECK_LOGIN).enqueue(object : Callback<Map<String, Any>>{
override fun onFailure(call: Call<Map<String, Any>>, t: Throwable) {
data.value = false
}
override fun onResponse(call: Call<Map<String, Any>>, response: Response<Map<String, Any>>) {
val result = response.body()
if (result != null && result.containsKey("success")){
val isLogin = result["success"] as Boolean
data.value = isLogin
}else{
data.value = false
}
}
})
return data
}
when logged in, popbackto LoadingFragment,isToLogin observe execute else immediately, LoginFragment start agagin. when I debug, wait a while on LoginFragment popBackStack, then goback to Loading Fragment,isToLogin observe execute true.so I am very confused, how can I fix this.
Finally, I solved this problem as follow.
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
fun checkLogin(): MediatorLiveData<Boolean> {
val isToLogin : MediatorLiveData<Boolean> = MediatorLiveData()
isToLogin.addSource(loadingRepository.checkLogin()) {
Logger.t(LoadingViewModel::class.java.simpleName).i("it = $it")
isToLogin.value = it
}
return isToLogin
}
}
then in LoadingFragment,:
loadingViewModel.checkLogin().observe(viewLifecycleOwner, Observer {
Logger.i("isToLogin = $it")
if (it) {
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
} else {
findNavController().navigate(R.id.start_login)
}
})

RxSwift - subscribe to a method

Is there a way with RxSwift to subscribe to a method which returns a completion block?
Example, let's have this object:
struct Service {
private var otherService = ...
private var initSucceeded = PublishSubject<Bool>()
var initSucceededObservale: Observable<Bool> {
return initSucceeded.asObservable()
}
func init() {
otherService.init {(success) in
self.initSucceeded.onNext( success)
}
}
}
And in a different place have a way to be notified when the service has been initialised:
service.initSucceededObservable.subscribe(onNext: {
[unowned self] (value) in
...
}).addDisposableTo(disposeBag)
service.init()
Would be there a simpler solution?
I like to use Variables for this sort of thing. Also, I'd recommend using class here because you're tracking unique states and not just concerning yourself with values.
class Service {
private let bag = DisposeBag()
public var otherService: Service?
private var isInitializedVariable = Variable<Bool>(false)
public var isInitialized: Observable<Bool> {
return isInitializedVariable.asObservable()
}
public init(andRelyOn service: Service? = nil) {
otherService = service
otherService?.isInitialized
.subscribe(onNext: { [unowned self] value in
self.isInitializedVariable.value = value
})
.addDisposableTo(bag)
}
public func initialize() {
isInitializedVariable.value = true
}
}
var otherService = Service()
var dependentService = Service(andRelyOn: otherService)
dependentService.isInitialized
.debug()
.subscribe({
print($0)
})
otherService.initialize() // "Initializes" the first service, causing the second to set it's variable to true.
You could use a lazy property:
lazy let initSucceededObservale: Observable<Bool> = {
return Observable.create { observer in
self.otherService.init {(success) in
observer.on(.next(success))
observer.on(.completed)
}
return Disposables.create()
}
}()
and then you can use:
service.init()
service.initSucceededObservable.subscribe(onNext: {
[unowned self] (value) in
...
}).addDisposableTo(disposeBag)
Let me know in the comments if you have problems before downvoting, thanks.

Swift protocol with associatedtype (PAT)

What I want to achieve is to wait for all service calls to complete. I know that it can be done with GCD, but I'm looking for more Object Oriented Approach. Here is what I've so far:
First services should notify delegate for their completion, so we will need a protocol for that:
protocol ParallelServiceDelegate: class {
func serviceDidCompleted()
}
Services are Alamofire requests and we are getting their response as:
enum ServiceResult {
case Success(NSDictionary)
case Failure(NSError)
}
My design is to add Facade (wrapper) over this methods. This is the abstract:
import ObjectMapper
protocol ParallelService: class {
associatedtype ItemType: Mappable
var item: ItemType? { get }
var error: NSError? { get }
var isCompleted: Bool { get }
weak var delegate: ParallelServiceDelegate? { get set }
// TODO: Pass params
func start()
func handleRequestCompletion(result: ServiceResult)
}
private var psiAssociationKey: UInt8 = 0
private var pseAssociationKey: UInt8 = 0
private var psdAssociationKey: UInt8 = 0
extension ParallelService {
var item: ItemType? {
return _item
}
var error: NSError? {
return _error
}
var isCompleted: Bool {
return item != nil || error != nil
}
weak var delegate: ParallelServiceDelegate? {
get {
let object = objc_getAssociatedObject(self, &psdAssociationKey)
let wrapper = object as? WeakWrapper<ItemType?>
return wrapper?.value as? ParallelServiceDelegate
}
set(newValue) {
objc_setAssociatedObject(self,
&psdAssociationKey,
WeakWrapper(value: newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
func handleRequestCompletion(result: ServiceResult) {
switch result {
case .Success(let json):
_item = map(json, object: ItemType.self)
case .Failure(let error):
_error = error
}
}
// Degfault is nothing
func start() {}
// MARK: - Private
private var _item: ItemType? {
get {
let object = objc_getAssociatedObject(self, &psiAssociationKey)
let wrapper = object as? WeakWrapper<ItemType?>
return wrapper?.value as? ItemType
}
set(newValue) {
objc_setAssociatedObject(self,
&psiAssociationKey,
WeakWrapper(value: newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
private var _error: NSError? {
get {
let object = objc_getAssociatedObject(self, &pseAssociationKey)
return object as? NSError
}
set(newValue) {
objc_setAssociatedObject(self,
&pseAssociationKey,
newValue,
.OBJC_ASSOCIATION_RETAIN)
}
}
}
And Specific Service Facade implementation:
class EmployeesParallelService: ParallelService {
typealias ItemType = Employee
func start() {
Service.emploeesList(callback: handleRequestCompletion)
}
}
class InformationParallelService: ParallelService {
typealias ItemType = Information
func start() {
Service.information(callback: handleRequestCompletion)
}
}
Service caller - Knows nothing about services it just starts all and waits for them to complete:
class ParallelServiceCaller {
private var services: [ParallelService] = []
// MARK: - Lifecycle
func addParallelService(service: ParallelService) {
service.delegate = self
self.services.append(service)
}
// MARK: - Public
func start() {
for service in services {
service.start()
}
}
}
extension ParallelServiceCaller: ParallelServiceDelegate {
func serviceDidCompleted() {
for service in services {
// !!! wait
if !service.isCompleted {
return
}
}
// TODO: Notify delegate
}
}
Latter I want to use it like this:
let caller = ParallelServiceCaller()
caller.addParallelService(EmployeesParallelService())
caller.addParallelService(InformationParallelService())
caller.start()
However I got problem in the implementation of the ParallelServiceCaller class. I'm getting the following error:
Protocol 'ParallelService' can only be used as a generic constraint
because it has Self or associated type requirements
Any idea how to avoid this error?
Update 07/07/16:
I'm still not able to understand how to use PATs. However I took slightly different approach and now I'm using visitor pattern. Here is my playground code, it may be helpful for someone:
//: Playground - noun: a place where people can play
import UIKit
// Mocks
enum ServiceResult {
case Success(NSDictionary)
case Failure(NSError)
}
protocol Mappable { }
typealias CompletionHandlerType = (result: ServiceResult) -> Void
class Service {
class func emploeesList(start: Int? = nil,
max: Int? = nil,
timestamp: Int? = nil,
callback: CompletionHandlerType) {
callback(result: .Success(NSDictionary()))
}
class func information(timestamp: Int? = nil,
callback: CompletionHandlerType) {
callback(result: .Failure(NSError(domain: "", code: 1, userInfo: nil)))
}
}
class EmployeesList: Mappable {}
class Information: Mappable {}
// Actual Implementation
// Visitor
protocol ParallelServiceCallerProtocol {
func call(service: EmployeesListParallelService)
func call(service: InformationParallelService)
}
// Element
protocol ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol)
}
// Concrete Elements
class EmployeesListParallelService: ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol) { visitor.call(self) }
}
class InformationParallelService: ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol) { visitor.call(self) }
}
// Concrete Visitor Delegate - defines callback for async tasks
protocol ParallelServiceCallerDelegateProtocol: class {
func didCompleteParellelServiceWithResult(service: ParallelServiceProtocol, result: ServiceResult)
}
// Concrete Visitor - make API calls
class ParallelServiceCaller <T: ParallelServiceCallerDelegateProtocol>: ParallelServiceCallerProtocol {
private unowned let delegate: T
init(delegate: T) {
self.delegate = delegate
}
func call(service: EmployeesListParallelService) {
Service.emploeesList { [unowned self] (result) in
self.delegate.didCompleteParellelServiceWithResult(service, result: result)
}
}
func call(service: InformationParallelService) {
Service.information { (result) in
self.delegate.didCompleteParellelServiceWithResult(service, result: result)
}
}
}
// Service Result In Context
enum SynchronizationServiceResult {
case Employees(ServiceResult)
case Information(ServiceResult)
}
// Concrete Visitor - Wraps API Result And Gives Context
class ParallelServiceParser: ParallelServiceCallerProtocol {
var result: SynchronizationServiceResult?
private let serviceResult: ServiceResult
init(serviceResult: ServiceResult) {
self.serviceResult = serviceResult
}
func call(service: EmployeesListParallelService) {
result = .Employees(serviceResult)
}
func call(service: InformationParallelService) {
result = .Information(serviceResult)
}
}
// Delegate that notifies for completion of all calls
protocol ParallelServiceManagerDelegateProtocol: class {
func didCompleteAllServicesWithResults(results: [SynchronizationServiceResult])
}
// Manager - starts all calls and adds context to returned results - knows nothing about calls
class ParallelServiceManager<T where T: ParallelServiceManagerDelegateProtocol> {
private let services: [ParallelServiceProtocol]
private unowned let delegate: T
// Keep Caller Visitors in Memory or they will be dealocated
private var callers: [ParallelServiceCaller<ParallelServiceManager>] = []
private var completed: [SynchronizationServiceResult] = [] {
didSet {
if completed.count == services.count {
self.delegate.didCompleteAllServicesWithResults(completed)
self.callers.removeAll()
}
}
}
init(services: [ParallelServiceProtocol], delegate: T) {
self.services = services
self.delegate = delegate
}
func start() {
visitAllServices { (service) in
let caller =
ParallelServiceCaller<ParallelServiceManager>(delegate: self)
service.start(caller)
self.callers.append(caller)
}
}
private func visitAllServices(perform: ParallelServiceProtocol -> () ) {
for service in self.services {
perform(service)
}
}
}
extension ParallelServiceManager: ParallelServiceCallerDelegateProtocol {
func didCompleteParellelServiceWithResult(service: ParallelServiceProtocol,
result: ServiceResult) {
// No need to persist parser visitor
let caller = ParallelServiceParser(serviceResult: result)
service.start(caller)
completed.append(caller.result!)
}
}
// Example Usage
class SynchronizationService {
private lazy var services: [ParallelServiceProtocol] = {
return [EmployeesListParallelService(), InformationParallelService()]
}()
func start() {
let manager = ParallelServiceManager<SynchronizationService>(services: services, delegate: self)
manager.start()
}
}
extension SynchronizationService: ParallelServiceManagerDelegateProtocol {
func didCompleteAllServicesWithResults(results: [SynchronizationServiceResult]) {
for result in results {
switch result {
case .Employees(let result):
// TODO:
print("\(result)") // Should Return Success
case .Information(let result):
// TODO:
print("\(result)") // Should Return Failure
}
}
}
}
let sync = SynchronizationService()
sync.start()

Resources