Xamarin.Forms DependencyService not for all platforms - xamarin

In the docs it is clearly stated that you need an implementation for all platforms:
You must provide an implementation in every platform project. If no
Interface implementation is registered, then the DependencyService
will be unable to resolve the Get<T>() method at runtime.
I haven't provided an implementation and so the app crashed. But what should I do in the case, that I don't need an implementation for that platform? Providing a bodyless method like this?
public void HideKeyboard()
{
// We need an implementation for the DependencyService, even it is empty.
}
Should I provide an implementation nevertheless?
public void HideKeyboard()
{
try
{
InputPane myInputPane = InputPane.GetForCurrentView();
myInputPane.TryHide();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
Or is the use of DependencyService the wrong option here?

I am assuming that you do not need an implementation on one platform, but do on the other.
There is basically two things you can do:
Provide a bodyless implementation on the platform where you don't need an actual implementation, like you suggested yourself
or; put a check for the platform around the DependencyService call.
If you need it only on iOS, just do it like this in your shared code:
if (Device.RuntimePlatform == Device.iOS)
DependencyService.Get<IKeyboardService>().HideKeyboard();
This way you don't litter your code with a redundant class. You do need to add this in all places where you're going to execute this code. So what the better option is here is up to you.
Using the DependencyService isn't wrong and actually the only way to reach this kind of platform specific functionality.

In addition to the two options Gerald mentioned of having a bodyless implementation or only calling Get on select platforms, you can also just check the return value of Get and only use the service if there is one. In code, this would be:
var ds = DependencyService.Get<IKeyboardService>();
if (ds != null)
ds.HideKeyboard();
or as a one-liner:
DependencyService.Get<IKeyboardService>()?.HideKeyboard();

Related

AutoMapper 11. Missing API replacements

28.2 ForAllMaps, ForAllPropertyMaps, Advanced and other “missing” APIs
Some APIs were hidden for normal usage. To light them up, you
need to add an using for AutoMapper. Internal and call the Internal
extension method on the configuration object. Most users don’t need
these advanced methods. Some expose internals and are not subject to
the usual semantic versioning rules. To avoid such tight coupling to
AutoMapper, you should try to stick to the public API.
I have ForAllMapps call in the project (NopCommerce 4.50.1). The original project uses AutoMapper 8.1.1. I want to update it to the newest package (11). What can I use instead (with example if possible).
Thank you.
For AutoMap 11, Some APIs were hidden for normal usage. To light them up, you need to add an using for AutoMapper.Internal and call the Internal extension method on the configuration object. Most users don’t need these advanced methods. Some expose internals and are not subject to the usual semantic versioning rules. To avoid such tight coupling to AutoMapper, you should try to stick to the public API.
ForAllMaps, ForAllPropertyMaps, Advanced and other “missing” APIs, use the API instead:
void ConfigurationExpression(IMapperConfigurationExpression cfg)
{
AutoMapper.Internal.InternalApi.Internal(cfg).ForAllMaps(MappingExpression);
}
https://docs.automapper.org/en/latest/11.0-Upgrade-Guide.html#forallmaps-forallpropertymaps-advanced-and-other-missing-apis
https://github.com/AutoMapper/AutoMapper/blob/9f2f16067ab201a5a8b9bc982f3a37e8790da7a0/src/AutoMapper/Internal/InternalApi.cs#L15
If they consider ForAllMaps to be too tightly coupled, you could write your own implementation:
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
this.CreateCustomMap<Model1, Model2>()
}
}
public static class ProfileExtensions
{
public static IMappingExpression<TFrom, TTo> CreateCustomMap<TFrom, TTo>(this Profile profile)
{
var mappingExpression = profile.CreateMap<TFrom, TTo>();
// do something like mappingExpression.ForMember(...)
return mappingExpression;
}
}

Where is the Terminal Operation sum() implemented in Java 8?

I see, that it's defined in Interface IntStream, but when you write IntStream.range(0, 200).sum(); how the implementation is called? where is it? couldn't find..
As for every interface, it's defined in the concrete class(es) that implement the interface.
In this case, it's in java.util.stream.IntPipeline, which is not a public class. But you shouldn't care about that. All you need to know is that an IntStream has that method, which does what the javadoc of the method does.
If you're really curious about its implementation, look in the source code of IntPipeline.java:
return reduce(0, Integer::sum);
Note on how I found out extremely easily: I just open the type hierarchy of IntStream in my IDE (IntelliJ, but all decent IDEs have that functionality), and notice that it has a single direct implementation: IntPipeline, which indeed contains the method.
If you are using an eligible compiler, there is an option to show its implementation. For example, when I want to see its implementation by IntelliJ, I click go to implementation. Then, it redirects.
In IntPipeLine.java,
#Override
public final int sum() {
return reduce(0, Integer::sum);
}

Can I Write a Spotlight Importer in Swift?

I need to write a Spotlight Importer for an application that I've written in Swift, and am referring to the official Apple guide for Writing a Spotlight Importer.
It seems straightforward enough, however creating a Spotlight Importer project creates a default setup for an Objective-C implementation. Now, working with Objective-C isn't a huge problem (I've used it plenty of times in the past) but everything I've written for my application is in Swift, so I'd really I'd like to write the importer in Swift too to avoid switching between languages, and also so I can share some of the code that I've already done for reading/writing files.
Firstly, is it possible to write a Spotlight Importer using Swift instead of Objective-C? And if it is, where should I start (e.g- if I take the Objective-C starting point, what would I do to switch over to Swift instead)?
It took me a bit of time to get this to work.
Instead of adding Swift code to the mdimporter, I import an embedded framework already setup for my app.
I removed all the example code except main.c and GetMetadataForFile.m.
In the latter I import my framework where all the functionality now resides as Swift code.
The built mdimporter is added to the app.
In the File Inspector set Location to Relative to Build Products.
The app then adds the mdimporter with a Copy Files Build Phase.
Destination: Wrapper
Subpath: Contents/Library/Spotlight
The following needs to be added to the Run Search Paths build setting, as we are linking to the app's embedded frameworks.
#loader_path/../../../../../Frameworks
If you get compiler error that the framework module can't be found when building the app, depending on how your workspace is set up, you might need to modify your app's Scheme.
Turn off Parallelize Build
Add the Build targets in this sequence:
Frameworks project(s)
mdimporter project
App project
The additional benefit of having all the logic in a framework, is that it can be prototyped and verified in a Playground. A million times easier than debugging an mdimporter plugin.
Yes, it is possible to write a Spotlight Importer entirely* in Swift!
*except for a few lines of code in main.m
I've just published one here: https://github.com/foxglove/MCAPSpotlightImporter
Here's a detailed blog post about the implementation process:
https://foxglove.dev/blog/implementing-a-macos-search-plugin-for-robotics-data
The difficult part of this is implementing a plugin that's compatible with the CFPlugIn architecture. (The MDImporter-specific logic is relatively minimal.) The CFPlugIn API is based on Microsoft's COM and Apple's docs are almost 20 years old.
The plugin is expected to be a block of memory conforming to a certain memory layout — specifically, the first value in the block must be a pointer to a virtual function table (vtable) for the requested interface (in the case of a MDImporter, this is either MDImporterInterfaceStruct or MDImporterURLInterfaceStruct) or the base IUnknown interface. This layout is documented here.
I wanted to organize the Swift code into a class, but you can't control the memory layout of a Swift class instance. So I created a "wrapper" block of memory which holds the vtable and an unsafe pointer to the class instance. The class has a static func allocate() which uses UnsafeMutablePointer to allocate the wrapper block, create and store the class instance in it, and also initialize the vtable.
The vtable implements the standard COM base interface (IUnknown) functions (QueryInterface, AddRef, and Release) by grabbing the class instance out of the wrapper and calling the queryInterface(), addRef(), and release() methods on the instance. It also implements the Spotlight-specific ImporterImportURLData function (or ImporterImportData). Unfortunately, in my testing, it seemed like Spotlight did not pass the correct pointer to the wrapper struct as the first argument to ImporterImportURLData, so it was impossible to call a method on the class instance, so the function that actually imports attributes for a file had to be a global function. For this reason I wasn't able to make the plug-in implementation a more generic class that could be used with any interface — it has to be tied to a specific global importer function.
I'd encourage you to view the full source on GitHub, but in the interest of not being a link-only answer, here's the core functionality:
final class ImporterPlugin {
typealias VTable = MDImporterURLInterfaceStruct
typealias Wrapper = (vtablePtr: UnsafeMutablePointer<VTable>, instance: UnsafeMutableRawPointer)
let wrapperPtr: UnsafeMutablePointer<Wrapper>
var refCount = 1
let factoryUUID: CFUUID
private init(wrapperPtr: UnsafeMutablePointer<Wrapper>, factoryUUID: CFUUID) {
self.wrapperPtr = wrapperPtr
self.factoryUUID = factoryUUID
CFPlugInAddInstanceForFactory(factoryUUID)
}
deinit {
let uuid = UUID(factoryUUID)
CFPlugInRemoveInstanceForFactory(factoryUUID)
}
static func fromWrapper(_ plugin: UnsafeMutableRawPointer?) -> Self? {
if let wrapper = plugin?.assumingMemoryBound(to: Wrapper.self) {
return Unmanaged<Self>.fromOpaque(wrapper.pointee.instance).takeUnretainedValue()
}
return nil
}
func queryInterface(uuid: UUID) -> UnsafeMutablePointer<Wrapper>? {
if uuid == kMDImporterURLInterfaceID || uuid == IUnknownUUID {
addRef()
return wrapperPtr
}
return nil
}
func addRef() {
precondition(refCount > 0)
refCount += 1
}
func release() {
precondition(refCount > 0)
refCount -= 1
if refCount == 0 {
Unmanaged<ImporterPlugin>.fromOpaque(wrapperPtr.pointee.instance).release()
wrapperPtr.pointee.vtablePtr.deinitialize(count: 1)
wrapperPtr.pointee.vtablePtr.deallocate()
wrapperPtr.deinitialize(count: 1)
wrapperPtr.deallocate()
}
}
static func allocate(factoryUUID: CFUUID) -> Self {
let wrapperPtr = UnsafeMutablePointer<Wrapper>.allocate(capacity: 1)
let vtablePtr = UnsafeMutablePointer<VTable>.allocate(capacity: 1)
let instance = Self(wrapperPtr: wrapperPtr, factoryUUID: factoryUUID)
let unmanaged = Unmanaged.passRetained(instance)
vtablePtr.initialize(to: VTable(
_reserved: nil,
QueryInterface: { wrapper, iid, outInterface in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
if let interface = instance.queryInterface(uuid: UUID(iid)) {
outInterface?.pointee = UnsafeMutableRawPointer(interface)
return S_OK
}
}
outInterface?.pointee = nil
return HRESULT(bitPattern: 0x8000_0004) // E_NOINTERFACE <https://github.com/apple/swift/issues/61851>
},
AddRef: { wrapper in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
instance.addRef()
}
return 0 // optional
},
Release: { wrapper in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
instance.release()
}
return 0 // optional
},
ImporterImportURLData: { _, mutableAttributes, contentTypeUTI, url in
// Note: in practice, the first argument `wrapper` has the wrong value passed to it, so we can't use it here
guard let contentTypeUTI = contentTypeUTI as String?,
let url = url as URL?,
let mutableAttributes = mutableAttributes as NSMutableDictionary?
else {
return false
}
var attributes: [AnyHashable: Any] = mutableAttributes as NSDictionary as Dictionary
// Call custom global function to import attributes
let result = importAttributes(&attributes, forFileAt: url, contentTypeUTI: contentTypeUTI)
mutableAttributes.removeAllObjects()
mutableAttributes.addEntries(from: attributes)
return DarwinBoolean(result)
}
))
wrapperPtr.initialize(to: (vtablePtr: vtablePtr, instance: unmanaged.toOpaque()))
return instance
}
}
Finally, I created an #objc class that exposes this allocate function to Obj-C, where I can call it from main.m, and return the pointer to the wrapper block from the factory function. This was necessary because I didn't want to use the unstable #_cdecl attribute to expose a Swift function directly to the plug-in loader.
#objc public final class PluginFactory: NSObject {
#objc public static func createPlugin(ofType type: CFUUID, factoryUUID: CFUUID) -> UnsafeMutableRawPointer? {
if UUID(type) == kMDImporterTypeID {
return UnsafeMutableRawPointer(ImporterPlugin.allocate(factoryUUID: factoryUUID).wrapperPtr)
}
return nil
}
}
// main.m
void *MyImporterPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) {
return [PluginFactory createPluginOfType:typeID factoryUUID:CFUUIDCreateFromString(NULL, CFSTR("your plugin factory uuid"))];
}
See my blog post for more details.
Since Apple introduced Swift as a language to be perfectly compatible with any existing Objective-C project I would suggest you just start with whatever makes things easier for you.
If you know Swift best then nothing keeps you from using that – for whatever project you might want. If you want to follow a tutorial that was written for Objective-C and not updated for Swift yet, I think you have two choices (I'd personally recommend going for the second option for now):
Write the same logic written in Objective-C within the tutorial now in Swift from scratch (nearly everything possible in Objective-C is easily possible with Swift, too). For that you need to understand the basics of Objective-C and the corresponding syntax and features in Swift though.
Start with Objective-C to follow the tutorial and keep things easier at the beginning (no need to really understand the tutorials details). Then use the great possibility of mix and matching Swift code alongside Objective-C code to customize the code for your needs or to extend it with your own pre-existing classes.
More specifically on the second option:
If you want to write new classes just use Swift – you can perfectly use everything written in Objective-C from within Swift and vice versa. If you feel you need to change classes already written in Objective-C you have these options: Extend the class written in Objective-C with a new Swift class, re-write that specific file in Swift or just edit the Objective-C file directly.
To learn more on how to mix and match Swift code alongside Objective-C I recommend reading Apples official documentation. It's part of the free iBook "Using Swift with Cocoa and Objective-C" written by Apple engineers for developers.
Unfortunately Apple actually does seem to provide their template for a Spotlight Importer from within XCode for Objective-C only at the moment. Don't know why this is though – I can't see anything stopping them from supporting Swift. We should probably report this with Apples Bug Reporter to stress the fact that people are actually asking for this.
Hope I didn't overlook anything here, otherwise my answer will be pointless. ^^
UPDATE (request)
Here are some steps on where to begin to implement the first approach:
First create a Spotlight Importer project with the latest XCode version
– Create a new "Cocoa Touch" class named exactly the same as your pre-created main Objective-C classes (e.g. "MySpotlightImporter")
Choose Swift and "Create Bridging Header" when asked during class creation
– Re-implement the code written in the ObjC-MySpotlightImporter class within the Swift class (you might want to create a Cocoa App with Core Data support in Swift and Objective-C to get some idea of their differences)
– I'm not sure if you can rewrite the GetMetaDataFile.m in Swift, too, I couldn't figure that out in my test, so you maybe need to keep it around (for now)
– In case you receive any errors along the way that point to some missing configuration just search for the related files/classes in the projects "Build settings" and apply your changes there
I hope this helps to get you started and is specific enough. I tried to do the necessary changes myself in order to provide an example project in Swift but unfortunately I couldn't get it working in a limited time. You may want to consider providing your code publicly though (e.g. on GitHub with a link posted here) in case you decide to port it yourself so others can profit from this, too.
Good luck!

An alternative to TaskEx.FromResult on a platform where it's not available

I am converting a portable class library to use a different profile (78). Most of the changes were related to reflection API, and now I have few last lines that don't compile, all of them are using TaskEx.FromResult.
TaskEx.FromResult is handy when a method returns Task, and a value of T needs to be wrapped and returned from the method, e.g.:
public Task<int> ReturnTaskOfInt()
{
return TaskEx.FromResult(42);
}
Unfortunately TaskEx is not available for some PCL profiles. Perhaps it shouldn't be hard to write a replacement for it, and I will appreciate an advise.
Oops, it was damn easy. TaskEx.FromResult is not available, but Task.FromResult is there.

Subjects vs Publish

I've been playing with Rx for the last day or so and one thing that still doesn't quite make sense to me is Subject.
When would I use this class directly?
It seems to me that I'd be more likely to expose an IObservable and use the Publish method to create an IConnectableObservable. I then have the option of using RefCount to convert back to an IObservable that monitors its subscriber.
I'm sure I'm missing something here. If you have any insight I'd appreciate it.
Thanks
You are correct that exposing Subject directly is unlikely to be what you want. Usually, you would use a Subject internally and expose it as an IObservable. One example of when you might do this is in this question.
I'm new to Rx as well, but I've used them for classes that have hot observables. ie.
class NumberMonitor
{
Subject<double> _source = new Subject<double>();
public IObservable<double> NumberStream {get {return _source.AsObservable; } }
void OnSomeEvent(object sender, EventArgs e)
{
_source.OnNext(1);
}
}
I've used AsyncSubject for some sort of completion flags internally in a ViewModel class as a quick fix, I would say they looked way cleaner than doing Observable.Create instead.

Resources