-1

I need a method to accept two types and do the same thing with them.

I know I could do:

public void myMethod(TextView tv, ReadableMap styles) {
    if (styles.hasKey("fontFamily")) {
        String font = "fonts/" + styles.getString("fontFamily") + ".ttf";
        Typeface tf = Typeface.createFromAsset(context.getAssets(), font);
        tv.setTypeface(tf);
    }
    if (styles.hasKey("fontSize")) {
        tv.setTextSize(styles.getInt("fontSize"));
    }
    if (styles.hasKey("letterSpacing")) {
        tv.setLetterSpacing(PixelUtil.toPixelFromDIP(styles.getInt("letterSpacing")));
    }
}

public void myMethod(TextPaint tv, ReadableMap styles) {
    // copy paste the **exact** same code
}

Both TextView and TextPaint have methods setTypeface(Typeface), setTextSize(float) and setLetterSpacing(float), but the two classes do not have a shared supertype other than Object. (Note that TextPaint inherits the methods from Paint.) These classes both separately declare these methods, with the same names and signatures.

So since I cannot cast one to another, I would like to know how to reduce this amount of duplicated code?

Radiodef
  • 35,285
  • 14
  • 78
  • 114
carla
  • 1,728
  • 1
  • 30
  • 35
  • 2
    Javadocs can be useful - they do exist in parent classes - they just don't share a common ancestor. – Andy Aug 03 '18 at 00:37
  • @KartikKaushik You should think think twice before you write such assertive afirmation. They do work already, what I am aiming is to create a helper to extract duplicated code. If you don't know what I am talking about you had better not saying anything instead of making misleading assumptions. – carla Aug 03 '18 at 01:03
  • 1
    You could access the methods with reflection... I'm not sure if I would actually do it in this case, but it's probably more maintainable than copy and pasting. – Radiodef Aug 03 '18 at 01:50
  • why you want to do this? force two classes that doesn't share a common ancestor to run through the same method just because they have a method with same signature? just to save few lines of code? this is a very bad idea achievable by reflection but tottally agains any design pattern and very easily breakable – Rafael Lima Aug 03 '18 at 03:34
  • 1
    @RafaelLima The Android API is designed this way. It's not a problem caused by the OP. – Radiodef Aug 03 '18 at 14:07

4 Answers4

1

Create an interface class which shares the common method signatures setTypeFace, setTextSize, setLetterSpacing. (Basically the Adapter pattern.)

public interface MyInterface {
   Typeface setTypeface(Typeface typeface);
   void setTextSize (float textSize);
   void setLetterSpacing(float letterSpacing);
}

Redefine the myMethod signature as

public void myMethod (MyInterface myi, ReadableMap styles) {
   //...change all your 'tv' references to myi
}

And then invoke with an anonymous class

// myTextView is in scope here...

myMethod(new MyInterface() {
      // partial implementation
      Typeface setTypeface(Typeface typeface) {
         myTextView.setTypeface(typeface);
      }, myStyles);
   }

// myTextPaint is in scope here   
myMethod(new MyInterface() {
      // partial implementation
      Typeface setTypeface(Typeface typeface) {
         myTextPaint.setTypeface(typeface);
      }, myStyles);
}

Or create wrappers classes implementing MyInterface for both the TextView and TextPaint.

Andy
  • 3,233
  • 3
  • 15
  • 29
0

I do apologise for my comment, I should have checked the super classes and should have used words which make my opinion visible instead of asserting something. To make this right, I tried the following, and it worked. Since I don't have Android SDK available at the moment, I created my own TextView and TextPaint classes and used reflection to call the methods because we already know their signature.

First, change your method signature to public void myMethod(Object tv, ReadableMap styles).

This shows how to call the setTextSize method, but the concept should be the same for all the other methods as well.

    tv.getClass()
            .getMethod("setTextSize", float.class)
            .invoke(tv, styles.getInt("fontSize"));

You can read more about invoking methods via their names using Java Reflection.

Kartik
  • 7,194
  • 3
  • 23
  • 43
  • Yeah, I tried Object class, but when I do that it complains that it 'Cannot resolve method' setTypeface, setTextSize and setLetterSpacing. – carla Aug 03 '18 at 17:38
  • @carla We are never calling those methods on `tv` directly, so you should not get that error. You need to replace the calls, for example, replace `tv.setTextSize(styles.getInt("fontSize"));` with the code snippet in my answer. So we are calling `getClass()` on `tv` instead of setTypeface, setTextSize and setLetterSpacing. If it's still a problem, can you please show the updated method and the full error? – Kartik Aug 05 '18 at 23:29
0

how about this

 public void myMethod(TextView tv, String styles, TextPaint tp) {
    //must tv == null -> tp != null or tv != null -> tp == null
    if(tv != null){
        tp = tv.getPaint();
    }

   //doSomthing
}
Taz
  • 360
  • 3
  • 14
  • I don't think this solves my problem. I always have tv, but sometimes it is a TextView and other times is a TextPaint. – carla Aug 03 '18 at 17:40
-2

This is easy to solve, but you need to understand the basic concept of polumorphism.
In order to this be possible you need to have a common ancesstor between these two types

public void myMethod(TextView tv, ReadableMap styles) {this.method(tv,styles);}

public void myMethod(TextPaint tv, ReadableMap styles) {this.method(tv,styles);}

private void method(CommonAncestor tv, ReadableMap styles){/*do your stuff*/}

A common ancestor is a Class or Interface which both of your classes extends or implements

This ancestor must provide BOTH of the common methods you want to call

  • If you are creating these classes in your project you can simple create the ancestor and make them extend

  • if they are builtin types you can check for their treeline and find, if two classes share a method with same name and same logic typically they share an ancesstor

  • if there is no such ancestor, and you want the creation of this "no repeatition" method, you must use reflection... but this is tottally a bad idea, some kind of code smell waiting for crash and i refuse myself to teach it
Rafael Lima
  • 1,834
  • 21
  • 63
  • 1
    The SDK classes `TextView` and `TextPaint` can't be changed, to *extend* or *implement* them from `CommonAncestor`. – Kartik Aug 03 '18 at 03:13
  • @KartikKaushik, what to say about your answer? telling the guy to use reflection to force method call by name... what if the next version the method signature change in one class and not in another? what if the method change in both? you will need to set proguard rules for this workaraound since android change the method names dinamically and the method may not be called `setTextSize` at runtime – Rafael Lima Aug 03 '18 at 03:22
  • 1
    "what if the next version the method signature change [...]?" Okay, so (1) standard libraries almost never do this because (2) a change like that would break ALL compiled code that depends on it, not just reflective code. Compiled code which called the method that was changed will throw a linkage error. Besides, it's very easy to just write a unit test for this and then the problem is solved. You should be writing unit tests anyway. – Radiodef Aug 03 '18 at 13:56