In my view model, I have two properties:
private val databaseDao = QuestionDatabase.getDatabase(context).questionDao()
val allQuestions: LiveData<List<Question>> = databaseDao.getAllQuestions()
I have observers set on "allQuestions" in my fragment and I'm noticing the observer is being called when I rotate the device. Even though the View Model is only being created once (can tell via a log statement in init()), the observer methods are still being called.
Why is this? I would think the point is to have persistency in the View Model. Ideally, I want the database questions to be only loaded once, regardless of rotation.
This happens because LiveData is lifecycle aware.
And When you rotate the screen you UI Controller [Activity/Fragment] goes through various lifecycle states and lifecycle callbacks.
And since LiveData is lifecycle aware, it updates the detail accordingly.
I have tried to explain this with following points:
When the UI Controller is offscreen, Live Data performs no updates.
When the UI Controller is back on screen, it gets current data.
(Because of this property you are getting above behavior)
When UI controller is destroyed, it performs cleanup on its own.
When new UI Controller starts observing live data, it gets current data.
add this check inside observer
if(lifecycle.currentState == Lifecycle.State.RESUMED){
//code
}
I have the same issue, after reading the jetpack guideline doc, I solve it. Just like what #SVK mentioned, after the rotation of the screen, activity/fragment were re-created.
Base on the solution https://stackoverflow.com/a/64062616,
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun observeForever(observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observeForever { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
#MainThread
override fun setValue(#Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
Related
How to get initial keyboard focus on an Android compose app?
My view looks like
Parent { Child { Button} }
I tried implementing it in the Parent composable function....
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
The following code woirks like a charm....
fun Modifier.requestInitialFocus() = composed {
val first = remember { FocusRequester() }
LaunchedEffect(first) {
delay(1)
first.requestFocus()
}
focusRequester(first)
}
Original:
This error does not happen when implementing it in the composable function, where the target element is a direct child....
So implementing it in the Child seems to be a solution....
I am using ReactiveSwift for a while but suddenly encountered a strange bug or something.
I am using an MVVM architecture and have a simple view controller and a view model for it. The VM has a single property of type Signal<Void, NoError>. In the VM's init I send to its observer an empty value. On the view controller's viewDidLoad I listen for that signal and print some text.
Here is the view model:
class SomeVCModel {
let (someSignal, someSignalObserver) = Signal<Void, NoError>.pipe()
init() {
print (Thread.isMainThread)
DispatchQueue.main.async {
self.someSignalObserver.send(value: ())
}
}
}
And here is the view controller:
class SomeVC {
var model: SomeVCModel!
override func viewDidLoad() {
super.viewDidLoad()
model = SomeVCModel()
model.someSignal.observeValues { (_) in
print ("Some Value")
}
}
}
In this case the print statement doesn't get called. However, when I send a value to an observer by explicitly defining the main thread from DispatchQueue everything works:
DispatchQueue.main.async {
self.someSignalObserver.send(value: ())
}
I can't figure out why is this happening. Init for the view model itself is called on the main thread, so what could cause this behaviour?
Thanks in advance.
Your model sends the value during initialization.
However, your viewDidLoad function sets the observer after it is initialized, so it is not ready to observe it. Subsequent calls will work since the observer is now configured.
You have a few options, but basically you need to change the sequence.
I am trying to detect clicks on an UIElement like a button using Reactive Cocoa, using RAC for the first time in MVVM architecture.
I set the rac_command for my button in my ViewController.
addContactBtn.rac_command = viewModel.addContact
My ViewModel does the following:
func init(){
self.addContact = RACCommand() {
(any:AnyObject!) -> RACSignal in
return RACSignal.createSignal({
(subscriber: RACSubscriber!) -> RACDisposable! in
print("creating viewModel")
let viewModel = ContactAddViewModel(services: self.services)
self.services.pushViewModel(viewModel)
return RACDisposable(block: {
})
})
}
}
However, the command is executed only once and then the button is in disabled state when I pop the view controller and come back to original viewController. How can detect the button click any number of times?
Obviously, you missed something and had a simple mistake.
RACCommand expects to a signal which will be alive when the button clicked and be disposed after click-business-logic (like create viewModel, then pushViewModel in the above) executed. That is to say each button clicking-event associates a signal, not shares one unique signal, but has common signal inner logic. If a signal does not achieve completion or error, the responding clicking-event is not finished such that the button is disabled.
The below modified codes could be correct.
func init(){
self.addContact = RACCommand() {
(any:AnyObject!) -> RACSignal in
return RACSignal.createSignal({
(subscriber: RACSubscriber!) -> RACDisposable! in
print("creating viewModel")
let viewModel = ContactAddViewModel(services: self.services)
self.services.pushViewModel(viewModel)
// if you like to expose some value
// subscriber.sendNext(value)
subscriber.sendCompleted() // which makes clicking-event finished
// maybe error occurs
// subscriber.sendError()
return RACDisposable(block: {
})
})
}
}
I would like to advise you to checkout CocoaAction and Action in ReactiveSwift, which are replacement for RACCommand of legacy ReactiveObjC.
I have a custom delegate that triggers certain events. For context, it's a bluetooth device that fires events arbitrarily. I'd like my view controllers to optionally subscribe to these events that get triggered by the device delegate.
It doesn't make sense that each view controller conforms to the custom delegate because that means the device variable would be local and would only fire in that view controller. Other view controllers wouldn't be aware of the change. Another concrete example would be CLLocationManagerDelegate - for example what if I wanted all view controllers to listen to the GPS coordinate changes?
Instead, I was thinking more of a global delegate that all view controllers can subscribe to. So if one view controller triggers a request, the device would call the delegate function for all subscribed view controllers that are listening.
How can I achieve this architectural design? Are delegates not the right approach? I thought maybe NotificationCenter can help here, but seems too loosely typed, perhaps throwing protocols would help makes things more manageable/elegant? Any help would be greatly appreciated!
You could have an array of subscribers that would get notified.
class CustomNotifier {
private var targets : [AnyObject] = [AnyObject]()
private var actions : [Selector] = [Selector]()
func addGlobalEventTarget(target: AnyObject, action: Selector) {
targets.append(target)
actions.append(action)
}
private func notifyEveryone () {
for index in 0 ..< targets.count {
if targets[index].respondsToSelector(actions[index]) {
targets[index].performSelector(actions[index])
}
}
}
}
Naturally, you'd have to plan further to maintain the lifecycle of targets and actions, and provide a way to unsubscribe etc.
Note: Also ideal would be for the array of targets and actions to be an of weak objects. This SO question, for instance, deals with the subject.
• NotificationCenter is first solution that comes in mind. Yes, it is loosely typed. But you can improve it. For example like this:
extension NSNotificationCenter {
private let myCustomNotification = "MyCustomNotification"
func postMyCustomNotification() {
postNotification(myCustomNotification)
}
func addMyCustomNotificationObserverUsingBlock(block: () -> ()) -> NSObjectProtocol {
return addObserverForName(myCustomNotification, object: nil, queue: nil) { _ in
block()
}
}
}
• Second solution would be to create some shared object, which will store all delegates or blocks/closures and will trigger them when needed. Such object basically will be the same as using NotificationCenter, but gives you more control.
I'm an iOS developer and I'm guilty of having Massive View Controllers in my projects so I've been searching for a better way to structure my projects and came across the MVVM (Model-View-ViewModel) architecture. I've been reading a lot of MVVM with iOS and I have a couple of questions. I'll explain my issues with an example.
I have a view controller called LoginViewController.
LoginViewController.swift
import UIKit
class LoginViewController: UIViewController {
#IBOutlet private var usernameTextField: UITextField!
#IBOutlet private var passwordTextField: UITextField!
private let loginViewModel = LoginViewModel()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButtonPressed(sender: UIButton) {
loginViewModel.login()
}
}
It doesn't have a Model class. But I did create a view model called LoginViewModel to put the validation logic and network calls.
LoginViewModel.swift
import Foundation
class LoginViewModel {
var username: String?
var password: String?
init(username: String? = nil, password: String? = nil) {
self.username = username
self.password = password
}
func validate() {
if username == nil || password == nil {
// Show the user an alert with the error
}
}
func login() {
// Call the login() method in ApiHandler
let api = ApiHandler()
api.login(username!, password: password!, success: { (data) -> Void in
// Go to the next view controller
}) { (error) -> Void in
// Show the user an alert with the error
}
}
}
My first question is simply is my MVVM implementation correct? I have this doubt because for example I put the login button's tap event (loginButtonPressed) in the controller. I didn't create a separate view for the login screen because it has only a couple of textfields and a button. Is it acceptable for the controller to have event methods tied to UI elements?
My next question is also about the login button. When the user taps the button, the username and password values should gte passed into the LoginViewModel for validation and if successful, then to the API call. My question how to pass the values to the view model. Should I add two parameters to the login() method and pass them when I call it from the view controller? Or should I declare properties for them in the view model and set their values from the view controller? Which one is acceptable in MVVM?
Take the validate() method in the view model. The user should be notified if either of them are empty. That means after the checking, the result should be returned to the view controller to take necessary actions (show an alert). Same thing with the login() method. Alert the user if the request fails or go to the next view controller if it succeeds. How do I notify the controller of these events from the view model? Is it possible to use binding mechanisms like KVO in cases like this?
What are the other binding mechanisms when using MVVM for iOS? KVO is one. But I read it's not quite suitable for larger projects because it require a lot of boilerplate code (registering/unregistering observers etc). What are other options? I know ReactiveCocoa is a framework used for this but I'm looking to see if there are any other native ones.
All the materials I came across on MVVM on the Internet provided little to no information on these parts I'm looking to clarify, so I'd really appreciate your responses.
waddup dude!
1a- You're headed in the right direction. You put loginButtonPressed in the view controller and that is exactly where it should be. Event handlers for controls should always go into the view controller - so that is correct.
1b - in your view model you have comments stating, "show the user an alert with the error". You don't want to display that error from within the validate function. Instead create an enum that has an associated value (where the value is the error message you want to display to the user). Change your validate method so that it returns that enum. Then within your view controller you can evaluate that return value and from there you will display the alert dialog. Remember you only want to use UIKit related classes only within the view controller - never from the view model. View model should only contain business logic.
enum StatusCodes : Equatable
{
case PassedValidation
case FailedValidation(String)
func getFailedMessage() -> String
{
switch self
{
case StatusCodes.FailedValidation(let msg):
return msg
case StatusCodes.OperationFailed(let msg):
return msg
default:
return ""
}
}
}
func ==(lhs : StatusCodes, rhs : StatusCodes) -> Bool
{
switch (lhs, rhs)
{
case (.PassedValidation, .PassedValidation):
return true
case (.FailedValidation, .FailedValidation):
return true
default:
return false
}
}
func !=(lhs : StatusCodes, rhs : StatusCodes) -> Bool
{
return !(lhs == rhs)
}
func validate(username : String, password : String) -> StatusCodes
{
if username.isEmpty || password.isEmpty
{
return StatusCodes.FailedValidation("Username and password are required")
}
return StatusCodes.PassedValidation
}
2 - this is a matter of preference and ultimately determined by the requirements for your app. In my app I pass these values in via the login() method i.e. login(username, password).
3 - Create a protocol named LoginEventsDelegate and then have a method within it as such:
func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String)
However this method should only be used to notify the view controller of the actual results of attempting to login on the remote server. It should have nothing to do with the validation portion. Your validation routine will be handled as discussed above in #1. Have your view controller implement the LoginEventsDelegate. And create a public property on your view model i.e.
class LoginViewModel {
var delegate : LoginEventsDelegate?
}
Then in the completion block for your api call you can notify the view controller via the delegate i.e.
func login() {
// Call the login() method in ApiHandler
let api = ApiHandler()
let successBlock =
{
[weak self](data) -> Void in
if let this = self {
this.delegate?.loginViewModel_LoginCallFinished(true, "")
}
}
let errorBlock =
{
[weak self] (error) -> Void in
if let this = self {
var errMsg = (error != nil) ? error.description : ""
this.delegate?.loginViewModel_LoginCallFinished(error == nil, errMsg)
}
}
api.login(username!, password: password!, success: successBlock, error: errorBlock)
}
and your view controller would look like this:
class loginViewController : LoginEventsDelegate {
func viewDidLoad() {
viewModel.delegate = self
}
func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String) {
if successful {
//segue to another view controller here
} else {
MsgBox(errMsg)
}
}
}
Some would say you can just pass in a closure to the login method and skip the protocol altogether. There are a few reasons why I think that is a bad idea.
Passing a closure from the UI Layer (UIL) to the Business Logic Layer (BLL) would break Separation of Concerns (SOC). The Login() method resides in BLL so essentially you would be saying "hey BLL execute this UIL logic for me". That's an SOC no no!
BLL should only communicate with the UIL via delegate notifications. That way BLL is essentially saying, "Hey UIL, I'm finished executing my logic and here's some data arguments that you can use to manipulate the UI controls as you need to".
So UIL should never ask BLL to execute UI control logic for him. Should only ask BLL to notify him.
4 - I've seen ReactiveCocoa and heard good things about it but have never used it. So can't speak to it from personal experience. I would see how using simple delegate notification (as described in #3) works for you in your scenario. If it meets the need then great, if you're looking for something a bit more complex then maybe look into ReactiveCocoa.
Btw, this also is technically not an MVVM approach since binding and commands are not being used but that's just "ta-may-toe" | "ta-mah-toe" nitpicking IMHO. SOC principles are all the same regardless of which MV* approach you use.
MVVM in iOS means creating an object filled with data that your screen uses, separately from your Model classes. It usually maps all the items in your UI that consume or produce data, like labels, textboxes, datasources or dynamic images. It often does some light validation of input (empty field, is valid email or not, positive number, switch is on or not) with validators. These validators are usually separate classes not inline logic.
Your View layer knows about this VM class and observes changes in it to reflects them and also updates the VM class when the user inputs data. All properties in the VM are tied to items in the UI. So for example a user goes to a user registration screen this screen gets a VM that has none of it's properties filled except the status property that has an Incomplete status. The View knows that only a Complete form can be submitted so it sets the Submit button inactive now.
Then the user starts filling in it's details and makes a mistake in the e-mail address format. The Validator for that field in the VM now sets an error state and the View sets the error state (red border for example) and error message that's in the VM validator in the UI.
Finally, when all the required fields inside the VM get the status Complete the VM is Complete, the View observes that and now sets the Submit button to active so the user can submit it. The Submit button action is wired to the VC and the VC makes sure the VM gets linked to the right model(s) and saved. Sometimes Models are used directly as a VM, that might be useful when you have simpler CRUD-like screens.
I've worked with this pattern in WPF and it works really great. It sounds like a lot of trouble setting up all those observers in Views and putting a lot of fields in Model classes as well as ViewModel classes but a good MVVM framework will help you with that. You just need to link UI elements to VM elements of the right type, assign the right Validators and a lot of this plumbing gets done for you without the need for adding all that boilerplate code yourself.
Some advantages of this pattern:
It only exposes the data you need
Better testability
Less
boilerplate code to connect UI elements to data
Disadvantages:
Now you need to maintain both the M and the VM
You still can't completely get around using the VC iOS.
MVVM architecture in iOS can be easily implemented without using third party dependencies. For data binding, we can use a simple combination of Closure and didSet to avoid third-party dependencies.
public final class Observable<Value> {
private var closure: ((Value) -> ())?
public var value: Value {
didSet { closure?(value) }
}
public init(_ value: Value) {
self.value = value
}
public func observe(_ closure: #escaping (Value) -> Void) {
self.closure = closure
closure(value)
}
}
An example of data binding from ViewController:
final class ExampleViewController: UIViewController {
private func bind(to viewModel: ViewModel) {
viewModel.items.observe(on: self) { [weak self] items in
self?.tableViewController?.items = items
// self?.tableViewController?.items = viewModel.items.value // This would be Momory leak. You can access viewModel only with self?.viewModel
}
// Or in one line:
viewModel.items.observe(on: self) { [weak self] in self?.tableViewController?.items = $0 }
}
override func viewDidLoad() {
super.viewDidLoad()
bind(to: viewModel)
viewModel.viewDidLoad()
}
}
protocol ViewModelInput {
func viewDidLoad()
}
protocol ViewModelOutput {
var items: Observable<[ItemViewModel]> { get }
}
protocol ViewModel: ViewModelInput, ViewModelOutput {}
final class DefaultViewModel: ViewModel {
let items: Observable<[ItemViewModel]> = Observable([])
// Implmentation details...
}
Later it can be replaced with SwiftUI and Combine (when a minimum iOS version in of your app is 13)
In this article, there is a more detailed description of MVVM
https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3