Glide transition doesn't works with listener? - android-glide

I have my Glide code below
Glide.with(this)
.load("https://flybubble.com/media/wysiwyg/images/home/mainpage-box-5L.jpg")
.transition(DrawableTransitionOptions.withCrossFade())
.apply(RequestOptions()
.placeholder(R.drawable.placeholder)
.centerCrop()
.transforms(CenterCrop(), RoundedCorners(1000)))
.into(my_image_view)
All works fine, where when the image is loaded, the withCrossFade() shows it fades in.
However, when I add listener to it as below
Glide.with(this)
.load("https://flybubble.com/media/wysiwyg/images/home/mainpage-box-5L.jpg")
.transition(DrawableTransitionOptions.withCrossFade())
.apply(RequestOptions()
.placeholder(R.drawable.placeholder)
.centerCrop()
.transforms(CenterCrop(), RoundedCorners(1000)))
.listener(object: RequestListener<Drawable>{
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
return false
}
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
my_image_view.setImageDrawable(resource)
return true
}
})
.into(my_image_view)
The withCrossFade() can no longer work. Is it because transition doesn't works with listener, or I miss something?

Found the way to do it is not through transition, but a Transition that feeds the target.onResourceReady instead.
Glide.with(this)
.load("https://flybubble.com/media/wysiwyg/images/home/mainpage-box-5L.jpg")
//.transition(DrawableTransitionOptions.withCrossFade()) //Remove this line
.apply(RequestOptions()
.placeholder(R.drawable.placeholder)
.centerCrop()
.transforms(CenterCrop(), RoundedCorners(1000)))
.listener(object: RequestListener<Drawable>{
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean {
return false
}
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
target.onResourceReady(resource, DrawableCrossFadeTransition(1000, !isFirstResource))
return true
}
})
.into(my_image_view)

Without consulting with Glide docs, I'd say that this is because you have overridden Glides part of the job. You took the Drawable that the library has fetched for you in onResourceReady and, instead of loading the image with Glide, you've forced it in your ImageView. In this example, I'd compare Glides function as just a drawable downloader, while you are the one that loads the image into a view.

Related

Spinner keeps last selected item

I have a spinner with an array adapter. The spinner is populated inside a fragment onCreateView().
spinner.setSelection(0)
spinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
}
override fun onNothingSelected(var1: AdapterView<*>?) {
}
}
Whenever I get back to the fragment and the spinner is created, the last selected item is selected when onItemSelected() is called automatically and ignoring the spinner.setSelection(0) call.
I have put many logs to see what is going, but I cannot understand why the lately selected item is the one being selected by default and not the one at position 0.
I solved the issue by setting a click listener on the drop down view and basically do the same stuff I was doing with the OnItemSelectedListener.
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = SpinnerItemChartDropdownBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
val item = getItem(position)
val root = binding.root
bindDropdown(root, item)
binding.setClickListener {
listener.onChartRangeSelected(item)
}
return root
}
One important stuff. You need to do something like this, to dismiss the drop down view after an item has been selected:
fun hideSpinnerDropDown(spinner: Spinner) {
try {
val method: Method = Spinner::class.java.getDeclaredMethod("onDetachedFromWindow")
method.isAccessible = true
method.invoke(spinner)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}

How to use a JavaFX binding to disable a button if TextField has invalid input

I got the following code from The Definitive Guide to Modern Java Clients with JavaFX:
updateButton.disableProperty()
.bind(listView.getSelectionModel().selectedItemProperty().isNull()
.or(wordTextField.textProperty().isEmpty())
.or(definitionTextArea.textProperty().isEmpty()));
I would like to modify it so the button is disabled if the String entered into frequencyTextField is not a nonnegative integer. I added a term to the conjunction as shown:
updateButton.disableProperty()
.bind(listView.getSelectionModel().selectedItemProperty().isNull()
.or(isLegalFrequency(frequencyTextField.textProperty()).not())
.or(wordTextField.textProperty().isEmpty())
.or(definitionTextArea.textProperty().isEmpty()));
Although it is probably not relevant, here is the method that tests validity:
private BooleanProperty isLegalFrequency(StringProperty sp) {
System.out.println("isLegalFrequency(" + sp.get() + ")");
try {
int value = Integer.parseInt(sp.get());
return new SimpleBooleanProperty(value >= 0);
} catch (NumberFormatException e) {
return new SimpleBooleanProperty(false);
}
}
My problem is that the button is always disabled. I have established that isLegalFrequency() is called only once, when the scene is being created. This makes sense, since I am passing frequencyTextField.textProperty(), not calling a method on it (which presumably sets up a listener behind the scenes).
Is there a way to modify the program without adding an explicit listener so it behaves as I'd like, or is it necessary to create a ChangeListener on frequencyTextField.textProperty?
Very generally, you can create an arbitrary method:
private Boolean validate() {
// arbitrary implementation here...
// in your case something like
if (listView.getSelectionModel().getSelectedItem() == null) return false ;
if (wordTextField.getText().isEmpty()) return false ;
if (definitionTextArea.getText().isEmpty()) return false ;
if (! isLegalFrequency(frequencyTextField.getText())) return false ;
return true ;
}
and then do
updateButton.disableProperty().bind(Bindings.createBooleanBinding(
() -> ! validate(),
listView.getSelectionModel().selectedItemProperty(),
frequencyTextField.textProperty(),
wordTextField.textProperty(),
definitionTextArea.textProperty()));
The parameters to the createBooleanBinding() method are a Callable<Boolean> (i.e. a method taking no parameters and returning a Boolean) followed by zero or more instances of javafx.beans.Observable (any property or ObservableList, etc, will work). You should include any property (or other observable) here that should trigger a recalculation of the disableProperty() when it changes.

How can I reflect to attribute but not trigger a rerender in lit-element?

I am converting a custom element dropdown over to lit-element. The way the existing element shows the dropdown options is by setting an expanded boolean attribute on the element, and the options are shown/hidden via css:
my-element:not([expanded]) .options-container {
display: none;
}
my-element[expanded] .options-container {
display: block;
}
The component doesn't need to do any rerenders because the logic is all in the css.
How can I achieve this behavior with lit-element, and not rerender the component? Rerendering can be costly if there are a lot of dropdown options.
I have tried implementing a shouldUpdate that returns false if only expanded has changed - but this causes lit-element not to reflect expanded to the attribute when set via a property, which is necessary in order to show/hide via css.
This is what I have, which doesn't work:
class MyDropdown extends LitElement {
static get properties() {
return {
expanded: { type: Boolean, reflect: true },
...
};
}
shouldUpdate(changedProperties) {
if (changedProperties.has('expanded') && changedProperties.size === 1) {
return false;
}
return true;
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Note that I am not using shadow dom yet, not sure if that would change the solution. I'm on lit-element 2.2.1.
The idea is to not use LitElement's static properties or #Property decorator. Write your own property implementation like this:
class MyDropdown extends LitElement {
_expanded = false;
get expanded() {
return this._expanded;
}
set expanded(val) {
this._expanded = val;
// Manually setting the property and reflecting attribute.
if (val) {
this.setAttribute('expanded', '');
} else {
this.removeAttribute('expanded');
}
}
// disable shadow-dom
createRenderRoot() {
return this;
}
}
Similarly, you can listen for attributeChangedCallback lifecycle event and adjust _expanded property whenever user changes the attribute and not property.

Why won't my SwiftUI Text view update when viewModel changes using Combine?

I'm creating a new watchOS app using SwiftUI and Combine trying to use a MVVM architecture, but when my viewModel changes, I can't seem to get a Text view to update in my View.
I'm using watchOS 6, SwiftUI and Combine. I am using #ObservedObject and #Published when I believe they should be used, but changes aren't reflected like I would expect.
// Simple ContentView that will push the next view on the navigation stack
struct ContentView: View {
var body: some View {
NavigationLink(destination: NewView()) {
Text("Click Here")
}
}
}
struct NewView: View {
#ObservedObject var viewModel: ViewModel
init() {
viewModel = ViewModel()
}
var body: some View {
// This value never updates
Text(viewModel.str)
}
}
class ViewModel: NSObject, ObservableObject {
#Published var str = ""
var count = 0
override init() {
super.init()
// Just something that will cause a property to update in the viewModel
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.count += 1
self?.str = "\(String(describing: self?.count))"
print("Updated count: \(String(describing: self?.count))")
}
}
}
Text(viewModel.str) never updates, even though the viewModel is incrementing a new value ever 1.0s. I have tried objectWillChange.send() when the property updates, but nothing works.
Am I doing something completely wrong?
For the time being, there is a solution that I luckily found out just by experimenting. I'm yet to find out what the actual reason behind this. Until then, you just don't inherit from NSObject and everything should work fine.
class ViewModel: ObservableObject {
#Published var str = ""
var count = 0
init() {
// Just something that will cause a property to update in the viewModel
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.count += 1
self?.str = "\(String(describing: self?.count))"
print("Updated count: \(String(describing: self?.count))")
}
}
}
I've tested this and it works.
A similar question also addresses this issue of the publisher object being a subclass of NSObject. So you may need to rethink if you really need an NSObject subclass or not. If you can't get away from NSObject, I recommend you try one of the solutions from the linked question.

Adding observer for KVO without pointers using Swift

In Objective-C, I would normally use something like this:
static NSString *kViewTransformChanged = #"view transform changed";
// or
static const void *kViewTransformChanged = &kViewTransformChanged;
[clearContentView addObserver:self
forKeyPath:#"transform"
options:NSKeyValueObservingOptionNew
context:&kViewTransformChanged];
I have two overloaded methods to choose from to add an observer for KVO with the only difference being the context argument:
clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, context: CMutableVoidPointer)
clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, kvoContext: KVOContext)
With Swift not using pointers, I'm not sure how to dereference a pointer to use the first method.
If I create my own KVOContext constant for use with the second method, I wind up with it asking for this:
let test:KVOContext = KVOContext.fromVoidContext(context: CMutableVoidPointer)
EDIT: What is the difference between CMutableVoidPointer and KVOContext? Can someone give me an example how how to use them both and when I would use one over the other?
EDIT #2: A dev at Apple just posted this to the forums: KVOContext is going away; using a global reference as your context is the way to go right now.
There is now a technique officially recommended in the documentation, which is to create a private mutable variable and use its address as the context.
(Updated for Swift 3 on 2017-01-09)
// Set up non-zero-sized storage. We don't intend to mutate this variable,
// but it needs to be `var` so we can pass its address in as UnsafeMutablePointer.
private static var myContext = 0
// NOTE: `static` is not necessary if you want it to be a global variable
observee.addObserver(self, forKeyPath: …, options: [], context: &MyClass.myContext)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &myContext {
…
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
Now that KVOContext is gone in Xcode 6 beta 3, you can do the following. Define a global (i.e. not a class property) like so:
let myContext = UnsafePointer<()>()
Add an observer:
observee.addObserver(observer, forKeyPath: …, options: nil, context: myContext)
In the observer:
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafePointer<()>) {
if context == myContext {
…
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
Swift 4 - observing contentSize change on UITableViewController popover to fix incorrect size
I had been searching for an answer to change to a block based KVO because I was getting a swiftlint warning and it took me piecing quite a few different answers together to get to the right solution. Swiftlint warning:
Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo).
My use case was to present a popover controller attached to a button in a Nav bar in a view controller and then resize the popover once it's showing - otherwise it would be too big and not fitting the contents of the popover. The popover itself was a UITableViewController that contained static cells, and it was displayed via a Storyboard segue with style popover.
To setup the block based observer, you need the following code inside your popover UITableViewController:
// class level variable to store the statusObserver
private var statusObserver: NSKeyValueObservation?
// Create the observer inside viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
statusObserver = tableView.observe(\UITableView.contentSize,
changeHandler: { [ weak self ] (theTableView, _) in self?.popoverPresentationController?.presentedViewController.preferredContentSize = theTableView.contentSize
})
}
// Don't forget to remove the observer when the popover is dismissed.
override func viewDidDisappear(_ animated: Bool) {
if let observer = statusObserver {
observer.invalidate()
statusObserver = nil
}
super.viewDidDisappear(animated)
}
I didn't need the previous value when the observer was triggered, so left out the options: [.new, .old] when creating the observer.
Update for Swift 4
Context is not required for block-based observer function and existing #keyPath() syntax is replaced with smart keypath to achieve swift type safety.
class EventOvserverDemo {
var statusObserver:NSKeyValueObservation?
var objectToObserve:UIView?
func registerAddObserver() -> Void {
statusObserver = objectToObserve?.observe(\UIView.tag, options: [.new, .old], changeHandler: {[weak self] (player, change) in
if let tag = change.newValue {
// observed changed value and do the task here on change.
}
})
}
func unregisterObserver() -> Void {
if let sObserver = statusObserver {
sObserver.invalidate()
statusObserver = nil
}
}
}
Complete example using Swift:
//
// AppDelegate.swift
// Photos-MediaFramework-swift
//
// Created by Phurg on 11/11/16.
//
// Displays URLs for all photos in Photos Library
//
// #see http://stackoverflow.com/questions/30144547/programmatic-access-to-the-photos-library-on-mac-os-x-photokit-photos-framewo
//
import Cocoa
import MediaLibrary
// For KVO: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12
private var mediaLibraryLoaded = 1
private var rootMediaGroupLoaded = 2
private var mediaObjectsLoaded = 3
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var mediaLibrary : MLMediaLibrary!
var allPhotosAlbum : MLMediaGroup!
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSLog("applicationDidFinishLaunching:");
let options:[String:Any] = [
MLMediaLoadSourceTypesKey: MLMediaSourceType.image.rawValue, // Can't be Swift enum
MLMediaLoadIncludeSourcesKey: [MLMediaSourcePhotosIdentifier], // Array
]
self.mediaLibrary = MLMediaLibrary(options:options)
NSLog("applicationDidFinishLaunching: mediaLibrary=%#", self.mediaLibrary);
self.mediaLibrary.addObserver(self, forKeyPath:"mediaSources", options:[], context:&mediaLibraryLoaded)
NSLog("applicationDidFinishLaunching: added mediaSources observer");
// Force load
self.mediaLibrary.mediaSources?[MLMediaSourcePhotosIdentifier]
NSLog("applicationDidFinishLaunching: done");
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
NSLog("observeValue: keyPath=%#", keyPath!)
let mediaSource:MLMediaSource = self.mediaLibrary.mediaSources![MLMediaSourcePhotosIdentifier]!
if (context == &mediaLibraryLoaded) {
NSLog("observeValue: mediaLibraryLoaded")
mediaSource.addObserver(self, forKeyPath:"rootMediaGroup", options:[], context:&rootMediaGroupLoaded)
// Force load
mediaSource.rootMediaGroup
} else if (context == &rootMediaGroupLoaded) {
NSLog("observeValue: rootMediaGroupLoaded")
let albums:MLMediaGroup = mediaSource.mediaGroup(forIdentifier:"TopLevelAlbums")!
for album in albums.childGroups! {
let albumIdentifier:String = album.attributes["identifier"] as! String
if (albumIdentifier == "allPhotosAlbum") {
self.allPhotosAlbum = album
album.addObserver(self, forKeyPath:"mediaObjects", options:[], context:&mediaObjectsLoaded)
// Force load
album.mediaObjects
}
}
} else if (context == &mediaObjectsLoaded) {
NSLog("observeValue: mediaObjectsLoaded")
let mediaObjects:[MLMediaObject] = self.allPhotosAlbum.mediaObjects!
for mediaObject in mediaObjects {
let url:URL? = mediaObject.url
// URL does not extend NSObject, so can't be passed to NSLog; use string interpolation
NSLog("%#", "\(url)")
}
}
}
}

Resources