How to turn a dart function name into a string? - list

for testing purposes, I want to be able to dynamically turn a function name like getUserLocationByUserID(String userID){} into a string 'getUserLocationByUserID' so I can use it elseWhere
So When you have a list of functions and you wanna iterate in them I wish to be able to do something like this
import 'package:flutter/material.dart';
void function1() {}
void function2() {}
void function3() {}
List<Function> functionsList = [function1, function2, function3];
String magicalFunctionThatGetsAStringOutOfAFunctionName(Function function) {
String functionNameAsAString = ''; // magical code here <----------------------------------------------
return functionNameAsAString;
}
class FunctionTester extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: functionsList.length,
itemBuilder: (context, index) => GestureDetector(
onTap: functionsList[index],
child: Container(
width: 300,
height: 300,
child: Text(
magicalFunctionThatGetsAStringOutOfAFunctionName(functionsList[index]),
maxLines: 1,
style: TextStyle(
fontSize: 10,
),
),
),
),
),
);
}
}

Give this a try:
String magicalFunctionThatGetsAStringOutOfAFunctionName(Function function) {
String functionNameAsAString = function.toString();
int s = functionNameAsAString.indexOf('\'');
int e = functionNameAsAString.lastIndexOf('\'');
return functionNameAsAString.substring(s + 1, e);
}
This solution might be a bit hacky but quite cheap compare to other options.
Note: This can be simplified, I wrote it this way to be easy to read :)

As a one-liner function:
String functionToString(Function fn) => fn.toString().split('\'')[1].trim();
void main() {
print(functionToString(functionToString));
}
Console results:
functionToString
As a get extension on Function:
void main() {
print(main.asString);
}
extension on Function {
String get asString => this.toString().split('\'')[1].trim();
}
Console:
main

This is possible in Dart using mirrors, however mirrors are not available in Flutter or Web (and could be discontinued - see https://github.com/dart-lang/sdk/issues/44489).
import 'dart:mirrors';
void function1() {}
void function2() {}
void function3() {}
List<Function> functionsList = [function1, function2, function3];
void main() {
for (final func in functionsList) {
ClosureMirror mirror = reflect(func);
final name = MirrorSystem.getName(mirror.function.simpleName);
print(name);
}
}
An alternative could be to use a map to store the function names with the function references, like:
void function1() {}
void function2() {}
void function3() {}
final functions = {
'function1': function1,
'function2': function2,
'function3': function3,
};
void main() {
for (final functionName in functions.keys) {
final function = functions[functionName];
print(functionName);
}
}

Since mirrors aren't available in Flutter, I suggest you take a look at code gen libraries, where you can programmatically generate code based on your code.

Related

Heterogeneous collection

In the new versions of C++, you can check if an item is in a unordered_set (a HashSet), even if that item is not the same type as the unordered_set, whilst maintaining O(1) time complexity.
I'm trying to find out how to do this in Swift.
Here is the C++ example:
struct First {
int data;
std::string otherData;
First(int data, std::string otherData) : data(data), otherData(otherData) { }
};
struct Second {
int data;
int otherData;
Second(int data, int otherData) : data(data), otherData(otherData) { }
};
Suppose I want to create an unordered_set of First, but I want to check if a Second object is in the Set, comparing by its data field. You could do this:
struct Equal {
using is_transparent = void;
template<class F, class S>
bool operator()(const F& lhs, const S& rhs) const {
return lhs.data == rhs.data;
}
};
struct Hash {
using is_transparent = void;
template<class T>
size_t operator()(const T& t) const {
return std::hash<int>{}(t.data);
}
};
int main()
{
std::unordered_set<First, Hash, Equal> set;
set.insert(First(100, "test"));
std::cout << set.contains(First(100, "bla")) << "\n"; // true
std::cout << set.contains(Second(100, 1000)) << "\n"; // true
}
And this works great. However, I'm not sure how you would achieve this in Swift. In Swift, a Set is the same thing as unordered_set, but its contains method only accepts that specific element (no overloads).
You could iterate through all the elements, but you lose the O(1) HashSet time complexity.
I was wondering, is this possible in Swift?
To meet the basic requirement (partial matching), you can use contains(where:) with a predicate to compare the hash values of elements to the hash of the target.
class First:Hashable {
var data:Int;
var otherData:String;
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable {
var data:Int;
var otherData:Int;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var set: Set = [First(data: 100, otherData: "test")];
print(set.contains(First(data: 100, otherData: "bla")));
var hasher = Hasher();
Second(data: 100, otherData: 1000).hash(into:&hasher);
var target = hasher.finalize();
print(set.contains(where: {(candidate:First) -> Bool in
var hasher = Hasher();
candidate.hash(into:&hasher);
return hasher.finalize() == target;
}));
To meet the performance requirement, there are (at least) two options: refactor the hashable data to a common base class, or write an extension method that creates a temporary element of the appropriate type with the hashable data.
Moving the hashable data to a base class is the most straight-forward, though the resultant Set will only be homogenous in the base class. Also, this approach can't be implemented if you don't have control over the source of the element classes.
Once the classes are defined, Set.contains(_:) will work as desired.
class Zeroth:Hashable {
var data:Int;
static func == (lhs:Zeroth, rhs:Zeroth) -> Bool {
return lhs.data == rhs.data;
}
init(_ data:Int) {
self.data = data;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class First:Zeroth {
var otherData:String;
init(data:Int, otherData:String) {
self.otherData = otherData;
super.init(data)
}
};
class Second:Zeroth {
var otherData:Int;
init(data:Int, otherData:Int) {
self.otherData = otherData;
super.init(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<Zeroth> = [test];
print(set.contains(bla));
var member = Second(data: 100, otherData: 1000);
print(set.contains(member));
An extension method gets the closest to the C++ interface. Use a protocol so the extension method can be constrained to classes that only hash some of their data. The protocol used below also adds a method, partialCopy(from:), that handles converting between classes.
protocol DataElement {
var data:Int {get}
init(_ data:Int)
static func partialCopy<Other:DataElement>(from other:Other) -> Self;
}
extension DataElement {
static func partialCopy<Other:DataElement>(from other:Other) -> Self {
return Self(other.data);
}
}
class First:Hashable, DataElement {
var data:Int;
var otherData:String = "";
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable, DataElement {
var data:Int;
var otherData:Int = 0;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<First> = [test];
print(set.contains(bla));
extension Set where Element:DataElement {
func contains<Other:DataElement>(matching member:Other) -> Bool {
let matching : Element = Element.partialCopy(from:member); //Element(member.data);
return self.contains(matching);
}
}
var other = Second(data: 100, otherData: 1000);
print(set.contains(matching:other));
Method #1
You can use an enum to store First and Second in the same set. You will have a case for First and a case for Second.
In the Hashable conformance for the enum, you should hash the data which is the same between both structs. The Equatable conformance just makes sure that if the hashes are equal, they are equivalent, even if the enum case is different.
Example:
enum Both: Hashable {
case first(First)
case second(Second)
func hash(into hasher: inout Hasher) {
switch self {
case .first(let first):
hasher.combine(first.data)
case .second(let second):
hasher.combine(second.data)
}
}
static func == (lhs: Both, rhs: Both) -> Bool {
lhs.hashValue == rhs.hashValue
}
}
struct First {
let data: Int
let otherData: String
}
struct Second {
let data: Int
let otherData: Int
}
let set: Set<Both> = [.first(First(data: 100, otherData: "test"))]
let first = First(data: 100, otherData: "bla")
print(set.contains(.first(first))) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(.second(second))) // true
Method #2
This may not be possible, if First and Second must be a struct. However, if they don't, you can have a superclass that does the Hashable conformance.
Example:
class Superclass: Hashable {
let data: Int
init(data: Int) {
self.data = data
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
static func == (lhs: Superclass, rhs: Superclass) -> Bool {
lhs.data == rhs.data
}
}
class First: Superclass {
let otherData: String
init(data: Int, otherData: String) {
self.otherData = otherData
super.init(data: data)
}
}
class Second: Superclass {
let otherData: Int
init(data: Int, otherData: Int) {
self.otherData = otherData
super.init(data: data)
}
}
let set: Set<Superclass> = [First(data: 100, otherData: "test")]
let first = First(data: 100, otherData: "bla")
print(set.contains(first)) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(second)) // true

How can i update swift variable when c++ callback function triggered

I would like to notify swift code whenever something changes or event triggered in my .cpp functions.
I've following structure in my app.
(.cpp)[.hpp] ->(.mm)[.h] -> .swift
I can handle changeThisString from swift code via :
let swiftString = CPPWrapper().MyMethodWrapper()
this is okay with button click / viewDidLoad but i would like to update this value whenever i set it up from c++.
If C++ passes new string to swift, it shouldn't wait the button click it should work like listener.
I'll be really glad for any kind of help, thank you.
Example:
my.cpp:
std::string changeThisString = "";
...
virtual void myCallBack(MyCallBackParam &paramter){
changeThisString = "I WANT TO SEE THIS MESSAGE ON MY APP!"
}
std::string MyClass::MyMethod() {
return changeThisString;
}
my.hpp:
#include <string>
class MyClass{
public:
std::string MyMethod();
};
wrapper.mm
#import "wrapper.h"
#import "my.hpp"
#implementation CPPWrapper
MyClass myClass;
- (NSString*) MyMethodWrapper {
NSString* result = [NSString stringWithUTF8String:myClass.MyMethod().c_str()];
return result;
}
#end
Wrapper.h
#import <Foundation/Foundation.h>
#interface CPPWrapper : NSObject
-(NSString*) MyMethodWrapper;
#end
.swift
let swiftString = CPPWrapper().MyMethodWrapper()
This is an example of C callback that triggers a Combine notification.
( Moved on GitHub right here : https://github.com/moosefactory/C-callback-to-Swift )
This example changes a value of a C String in a C Library and displays it in a field in a SwiftUI view.
You don't need to go through Objective-C.
The first part is the C Library ( Few changes to make it work with C++ )
The second part is the C<->Swift class
I think it's nice to have an object that makes the bridge between your C code and your swift application, to remove esoteric syntax from the app code. In this example, the file MyCLibraryInterfacedoes the job.
This class is an observable object that will publish the value change using combine, so it goes a bit beyond the question - You can stop there and do what you want once you are in the callback block. Note that we can't catch the swift context in c calls ( no calls to self or variables declared on the heap )
The third part is a simple SwiftUI app that receive change and update interface
C Library
MyCLibrary.h
#ifndef MyCLibrary_h
#define MyCLibrary_h
#include <stdio.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>
/// The callback to call when the string is changed
typedef void callback_t(const char* string);
void setCallBack(callback_t _callback);
/// A function that will change the string
void setString(const char* string);
void startTimer(void);
void cancelTimer(void);
#endif /* MyCLibrary_h */
MyCLibrary.c
#include "MyCLibrary.h"
const char* myString;
dispatch_queue_t queue;
dispatch_source_t timer;
bool running;
callback_t* callback;
void setCallBack(callback_t _callback) {
callback = _callback;
}
void setString(const char* string) {
myString = string;
callback(myString);
}
/// A function that will start a timer that changes string
int ticks = 0;
void idle(dispatch_source_t timer)
{
ticks++;
char ticksStr[32];
sprintf(ticksStr, "Time : %ds", ticks);
setString(ticksStr);
}
void startTimer() {
if (running) { cancelTimer(); sleep(1); }
queue = dispatch_queue_create("timerQueue", 0);
// Create dispatch timer source
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_event_handler(timer, ^{idle(timer);});
dispatch_source_set_cancel_handler(timer, ^{
dispatch_release(timer);
dispatch_release(queue);
});
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);
// Set timer
dispatch_source_set_timer(timer, start, NSEC_PER_SEC, 0);
ticks = 0;
running = true;
dispatch_resume(timer);
}
void cancelTimer() {
running = false;
dispatch_source_cancel(timer);
char ticksStr[32];
sprintf(ticksStr, "Canceled after %ds", ticks);
setString(ticksStr);
}
C<>Swift Part
MyApp-Bridging-Header.h
#import "MyCLibrary.h"
MyCLibraryInterface.swift
import Foundation
class MyCLibraryInterface: ObservableObject {
#Published var string: String = "This is a string"
static let shared = MyCLibraryInterface()
init() {
setCallBack { stringPtr in
let newString = CFStringCreateWithCString(kCFAllocatorDefault, stringPtr, kCFStringEncodingASCII) ?? "" as CFString
DispatchQueue.main.async {
MyCLibraryInterface.shared.string = newString as String
}
}
}
func setLibString(string: String) {
string.withCString { stringPointer in
setString(stringPointer)
}
}
func startLibTimer() {
startTimer()
}
func cancelLibTimer() {
cancelTimer()
}
}
SwiftUI Sample
This sample app present the intial string and a button. On click or tap, the setString function is called in the CLibrary, the swift callback is called and the view is updated following the ObservableObject modification
import SwiftUI
struct ContentView: View {
#ObservedObject var myCLibInterface: MyCLibraryInterface = MyCLibraryInterface.shared
var body: some View {
VStack {
Text(myCLibInterface.string).frame(width:150).padding()
Button("Reset") {
myCLibInterface.setLibString(string: "C Timer Example")
}.padding()
Button("Start Timer") {
myCLibInterface.startLibTimer()
}.padding()
Button("Cancel") {
myCLibInterface.cancelLibTimer()
}.padding()
}.padding(20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Flutter: Create button for each item in a list of strings (Dart)

I have a list of Strings (called namesList). For each String inside the list I want to create a button with one of the names.
As you can see in the code below, I have tried to make use of the for loop. However, when playing the code I can only see the first Button with the text "Anna".
import 'package:flutter/material.dart';
class ButtonsWithName extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _ButtonsWithNameState();
}
}
class _ButtonsWithNameState extends State<ButtonsWithName> {
String name1;
String name2;
String name3;
String name4;
String name5;
var namesList = new List<String>();
#override
void initState() {
name1 = 'Anna';
name2 = 'Bernd';
name3 = 'Christina';
name4 = 'David';
name5 = 'Elena',
namesList.add(name1);
namesList.add(name2);
namesList.add(name3);
namesList.add(name4);
namesList.add(name5);
super.initState();
}
Widget _buildButtonsWithNames() {
for(int i=0; i < namesList.length; i++){
RaisedButton(child: Text(answerList[0]));
}
return RaisedButton[i];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Wrap(children: <Widget>[
_buildButtonsWithNames();
]);
)
}
}
I expect to have 5 RaisedButtons in the end with the texts of the String-list, namely a button with the text "Anna", a button with the text "Bernd", and so and so forth.
I would really I appreciate anyone's help on this matter!
The result you got is normal, because your _buildButtonsWithNames() list return just one button, instead of list of buttons. So the solution is to create this list, fill it and then return it. See below how it should be:
import 'package:flutter/material.dart';
class ButtonsWithName extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _ButtonsWithNameState();
}
}
class _ButtonsWithNameState extends State<ButtonsWithName> {
String name1;
String name2;
String name3;
String name4;
String name5;
var namesList = new List<String>();
List<RaisedButton> buttonsList = new List<RaisedButton>();
#override
void initState() {
super.initState();
name1 = 'Anna';
name2 = 'Bernd';
name3 = 'Christina';
name4 = 'David';
name5 = 'Elena';
namesList.add(name1);
namesList.add(name2);
namesList.add(name3);
namesList.add(name4);
namesList.add(name5);
}
List<Widget> _buildButtonsWithNames() {
for (int i = 0; i < namesList.length; i++) {
buttonsList
.add(new RaisedButton(onPressed: null, child: Text(namesList[i])));
}
return buttonsList;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Wrap(children: _buildButtonsWithNames()));
}
}
This works for me, thanks to previous answer with some improvements:
import 'package:flutter/material.dart';
import 'package:audioplayers/audio_cache.dart';
void main() => runApp(XylophoneApp());
class XylophoneApp extends StatelessWidget {
final player = AudioCache();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Container(
child: PlaySoundButtons()
),
),
),
);
}
}
class PlaySoundButtons extends StatefulWidget {
#override
_PlaySoundButtonsState createState() => _PlaySoundButtonsState();
}
class _PlaySoundButtonsState extends State<PlaySoundButtons> {
List<String> soundNames = [
'note1.wav',
'note2.wav',
'note3.wav',
'note4.wav',
'note5.wav',
'note6.wav',
'note7.wav',
];
List<FlatButton> buttonsList = [];
final player = AudioCache();
#override
void initState() {
super.initState();
for (int i = 0; i < soundNames.length; i++) {
buttonsList.add(new FlatButton(onPressed: () {
player.play(soundNames[i]);
}, child: Text(soundNames[i])));
}
}
#override
Widget build(BuildContext context) {
return Wrap(children: buttonsList);
}
}

List [index +1] works [index - 1] doesn't (Flutter)

I have a method to skip to the next list item, which works fine, but the method to display the previous list item doesn't seem to re-render.
When I print to console, the list item index it goes down by 1, but the text widget doesn't update like it does when it increases by 1.
I have shown the 2x methods below and an excerpt from the build. Help! :)
void _skipFlashcard () {
setState(() {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
var nextFlashcard = allFlashcards[currentIndex + 1];
widget.flashcardToShow = nextFlashcard;
print(widget.flashcardToShow.flashcardId);
});
}
void _previousFlashcard () {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
var previousFlashcard = allFlashcards[currentIndex - 1];
widget.flashcardToShow = previousFlashcard;
print(widget.flashcardToShow.flashcardId);
}
-------------------------
Container(
child: Row(
children: <Widget>[
Text(widget.flashcardToShow.flashcardId.toString()),
Wrap your code in setState, that's all that is missed :-)
void _previousFlashcard () {
setState() {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
var previousFlashcard = allFlashcards[currentIndex - 1];
widget.flashcardToShow = previousFlashcard;
print(widget.flashcardToShow.flashcardId);
}
}

Famo.us Pass Event to another view

I got three js Files, AppView, NavigationMenuView, and HeaderView.
I'm trying to send an event to My HeaderView when a button is pressed in NavigationMenuView. Here is what I got so far. When I click a button in navigation view an event is emitted called "settings". Then Appview catches the Event. I have a hard time trying to get this event then passed to HeaderVew.
Code Is below.
Thanks
AppView Code
function AppView() {
View.apply(this, arguments);
this.menuToggle = false;
CreateHeaderView.call(this);
CreateNavigatinMenuView.call(this);
SetListeners.call(this);
}
AppView.prototype = Object.create(View.prototype);
AppView.prototype.constructor = AppView;
function CreateHeaderView() {
this.HeaderView = new HeaderView();
this.HeaderModifier = new StateModifier();
this.add(this.HeaderModifier).add(this.HeaderView);
}
function CreateNavigatinMenuView() {
this.NavigationView = new NavigationMenuView();
this.NavigationViewModifier = new StateModifier();
this.NavigationViewModifier.setTransform(
Transform.translate(-dimensions[0], 0, 0), {}
);
this.add(this.NavigationViewModifier).add(this.NavigationView);
}
function SetListeners() {
this.NavigationView.on('settings', function () {
//HERE IS WHERE I WHOULD EMIT TO HEADER VIEW
}.bind(this.HeaderView));
}
module.exports = AppView;
});
AppView
function SetListeners() {
this.settingsButton.on('click', function () {
this._eventOutput.emit('settings');
}.bind(this));
}
HeaderView
context.on('settings', function () {
alert("Test");
}.bind(this));
I'm assuming the second `SetListeners' is from NavigationMenuView (instead of AppView as mentioned above)?
I think in AppView, the code should be:
function SetListeners() {
this.NavigationView.on('settings', function () {
this.HeaderView._eventOutput.emit('settings');
}.bind(this));
}
And in HeaderView:
this.on('settings, function () {
// your code
}
The Timbre example on famo.us also contains good examples of event-piping:
Timbre example