Manton Reece
About Archive Photos 30 days Replies Reading Search Also on Micro.blog
  • React Native diary #6: SF Symbols

    The goal in my experiments with React Native is to build an app that feels as native to iOS as possible. Early on I tried to use SF Symbols and couldn’t get it to work, so I dropped in some custom icons instead. But the more I used the new version of Epilogue on my iPhone, the more this compromise bugged me.

    I briefly considered exporting the symbols I need as SVGs or PNGs and using them in the app only on iOS, but that’s against Apple’s license. Not worth even the small risk. Sticking to real SF Symbols also ensures the icons match other apps if Apple redesigns them slightly in future versions of iOS.

    I revisited using SF Symbols today with the component birkir/react-native-sfsymbols. A default install shows a runtime error when attempting to load a symbol. Luckily there’s a work-around mentioned in one of the issues.

    I copied this file from the repo and saved it as SFSymbols.tsx. Then instead of importing from the Node module, I just import that file:

    import { SFSymbol } from "./SFSymbols";
    

    It works! Here’s a quick screenshot of my navigation bar now:

    SF Symbols in navbar sccreenshot
    → 12:44 PM, Feb 16
  • React Native diary #5: misc observations

    Some thoughts after about a week of using React Native to rewrite Epilogue. It’s going well and has been fun to work in a new framework, knowing that it’s going to contribute to our long-term plan for mobile apps in Micro.blog. It’s not a throw-away distraction.

    It’s only by diving in that I’ve realized how surface-level my experience with JavaScript really is. I’ve taken some time to experiment more with JavaScript promises and async functions. Years ago I bought the book JavaScript: The Good Parts, but I’m not sure I did more than skim it, and it’s probably way out of date for modern JavaScript.

    I got pretty far into development over the last week even though I barely understood some of the React features I was using, like useState. That’s okay. After I ship Epilogue 1.1, I’m going to go back and clean up a few things.

    Xcode builds are slow and spin up the fans on my Intel-based MacBook Pro every time, but iterating on the UI and features is extremely fast because the JavaScript code can reload while the iOS app is running. In most cases, the UI refreshes automatically in the iOS Simulator when I save my Styles.js file in Nova. I rarely need to run a build in Xcode.

    App file sizes are bigger, but not enough to matter. Epilogue 1.0 was a tiny 0.5 MB, and my latest build of Epilogue 1.1 is 2.7 MB. A much more full-featured app, Gluon for Micro.blog, is 8.6 MB. This appears to be a non-issue.

    React Native is not new. It’s nice not to be on the cutting edge, because by now every problem I’m likely to run into has been solved. Some things I thought might be hard were easy enough, like handling the epilogue:// custom URL scheme.

    I’ve collected all the blog posts in this series in a category on my blog.

    → 2:28 PM, Feb 15
  • React Native diary #4: modals

    I’m continuing to plow ahead with the Epilogue rewrite. I’ve added a storage class to wrap AsyncStorage, making it easier to persist various bits of data. I’ve added searching for books using the Google Books API, largely copying JavaScript code from the previous version. The fetch() calls are the same between web browser-based JavaScript and React Native.

    Today I’m working on adding the posting screen. This shows a modal text view with some default blog post text for the current book, so you can quickly blog about what you’re reading:

    New post screenshot

    Just like the navigation controller, the modal uses React Navigation so that it looks and feels the same as any native iOS app. In the JSX, we add a group containing the new modal screen declaration:

    <NavigationContainer>
    	<Stack.Navigator>
    		<Stack.Group>
    			...
    		</Stack.Group>
    		<Stack.Group screenOptions={{ presentation: "modal" }}>
    			<Stack.Screen name="Post" component={PostScreen} />
    		</Stack.Group>
    	</Stack.Navigator>
    </NavigationContainer>
    

    For this version, I’m using a simple multi-line TextInput class. This appears to use a real UITextView under-the-hood.

    One thing I’d like to experiment with later is integrating the Markdown syntax highlighting Objective-C code from the current Micro.blog app, making it a component that we could use in multiple apps. It obviously wouldn’t work on Android, but we’ll need something like it when we’re ready to switch the official Micro.blog iOS app over to React Native.

    → 1:45 PM, Feb 14
  • React Native diary #3: dark mode

    Even though Epilogue 1.0 was simple, it did support Dark Mode on iOS. I always run my phone in Dark Mode, so I didn’t want to lose that when rewriting Epilogue in React Native.

    There are a handful of helper utilities to access iOS-only features in React Native, like Platform.isPad . There’s also useColorScheme, which can be used to check if we’re running in Dark Mode:

    const is_dark = (useColorScheme() == "dark");
    

    There doesn’t appear to be built-in support in React Native to use different sets of styles automatically. There are, however, at least a few third-party libraries to make this easier, including various dynamic stylesheets and React Navigation’s themes. I started to go down that rabbit hole, then stopped… There’s a lot to learn and I’d rather adopt a quick “worse is better” approach to first get a feel for how painful it is to style my views manually.

    I have a Styles.js source file that looks like this, controlling layout and colors for UI elements in Epilogue:

    import { StyleSheet } from "react-native";
    
    export default StyleSheet.create({
      bookTitle: {
        marginTop: 8,
        paddingLeft: 7
      },
      bookAuthor: {
        paddingTop: 4,
        paddingLeft: 7,
        color: "#777777"
      },
      ...
    }
    

    To use this style in JSX, I have something like this:

    <Text style={styles.bookAuthor}>Neil Gaiman</Text>
    

    To support Dark Mode, I’ve added a special “dark” field to the styles object. This will only have style properties that I want to override from the default light mode. In the case of bookAuthor, there’s no need to change the padding, just the text color:

    bookAuthor: {
      paddingTop: 4,
      paddingLeft: 7,
      color: "#777777"
    },
    dark: {
      bookAuthor: {
        color: "#FFFFFF"
      }
    }
    

    Back to the JSX, I check my is_dark variable and then reference a different set of styles. JSX lets me pass an array of styles, so we’ll include both the light mode version (styles.bookAuthor) and then the dark value (styles.dark.bookAuthor) that will override the color:

    <Text style={is_dark ? [ styles.bookAuthor, styles.dark.bookAuthor ] : styles.bookAuthor}>Neil Gaiman</Text>
    

    Here are a couple screenshots showing each mode side by side:

    Epilogue light screenshot Epilogue dark screenshot

    The JSX code is admittedly a little clunky. I can see how it could be cleaned up and more readable with other solutions. But the app only has a few screens, so I’m going to run with this for now.

    → 2:26 PM, Feb 13
  • React Native diary #2: state

    I’ve been programming the Mac for over 25 years, but I’m stumbling through React Native and JavaScript like a newbie. I’ve always found the best way to learn is by doing. Hit some brick walls, dig under them, and then realize later that you built the wall yourself, fighting the frameworks.

    One of the benefits of React Native or SwiftUI is a formal way to manage state, letting the frameworks update the UI for you when something changes. I’ve never thought of this as a big advantage, but maybe I’ll warm up to it.

    As I work on rewriting Epilogue, I’ve improved the book details screen to include a list of your bookshelves. Tap a bookshelf to add the current book to that bookshelf. A progress spinner will show while Epilogue sends the book data to Micro.blog.

    In the world of UIKit, I would probably have a reference to a UIActivityIndicatorView. When I’m ready to send the web request, I’d show and start the progress spinner by calling startAnimating() on it.

    In React Native, I have a boolean state that keeps track of whether the progress spinner should be animating, defaulting to false:

    const [ progressAnimating, setProgressAnimating ] = useState(false);
    

    Then when the button is pressed, I set the state to true and carry on with the web request:

    function addToBookshelf(bookshelf_id) {
      setProgressAnimating(true);
    
      // send book data to Micro.blog
      // ...
    }
    

    In the UI, the JSX references this boolean. The UI will automatically update whenever the value changes. I don’t need to hold a reference to the actual ActivityIndicator object anywhere in my JavaScript code:

    <ActivityIndicator size="small" animating={progressAnimating} />
    

    Here’s a 3-second video of how this looks in the app:

    Next up: I need to add sign-in back to the app before I can do a beta. I’ll also be working on Dark Mode and the search box.

    → 5:12 PM, Feb 12
  • React Native diary #1

    In the spirit of Brent’s Sync Diary series from 2013-2014, I’m going to blog a little about our decision to move away from UIKit for some of our Micro.blog apps. I’m new to React’s way of thinking about UI, and I barely use SwiftUI either, but it’s just code and I’ve been able to make some progress learning React Native already.

    First, to clear up some potential confusion: we are not abandoning iOS! I still love my iPhone, even if I’m very frustrated with how Apple is treating developers. We are embracing Android more fully, and limiting how much time we spend in Apple-only frameworks. Our iOS apps will still be the best we can make them.

    Micro.blog for Android is a pretty big project even in beta. Vincent hit the ground running. It’s a little daunting for me to wrap my head around until I’m more comfortable with React Native, so I started somewhere simple: our app Epilogue, which I built a couple months ago for both iOS and Android with HTML, JavaScript, and a sprinkling of native Swift and Kotlin.

    Epilogue 1.0 was developed very quickly. While I was happy to ship the app to customers, I wasn’t too happy about the UI. It was a little clunky, and it started to feel more clunky the more I used it regularly for my own book tracking. React Native uses JavaScript but with native views instead of web views, so I was pretty sure a rewrite would fix the problems in the UI.

    I started with the placeholder JavaScript you get from running npx react-native init, then added in Epilogue elements like the book list and querying the Micro.blog API. I also integrated react-native-menu/menu to get a native UIMenu-based context menu on iOS, something I couldn’t do before with the HTML-based app.

    The toolchain for React Native makes me a little nervous. It uses every package manager you can think of: Node, Yarn, Ruby Gems, CocoaPods… It feels fragile, but there are so many thousands of developers using this framework, I’m also not very worried about it breaking.

    Here are a couple of screenshots of the in-progress new version of Epilogue:

    Epilogue book list screenshot Epilogue book details screenshot

    It’s already better. The learning curve for me is getting used to how React shares state and React’s JSX markup, a mashup of XML for view layout with bits of JavaScript. I’m sure I’m not doing it quite right, but already I have something that works well as a foundation for Epilogue 1.1.

    → 9:47 AM, Feb 11
  • Moving away from App Store-focused development

    A few things are happening at once that together are putting some clarity on the direction we should have for Micro.blog development:

    • Vincent has been working on the official Android app. It’s in public beta now on Google Play. This app is written in React Native, which uses JavaScript with native system controls.
    • Apple is burning through developer trust with the App Store. This is now mainstream opinion, not just a fringe of developers complaining. Apple’s policy in the Netherlands is the latest.
    • Frameworks for Apple platforms are fragmented. UIKit vs. SwiftUI vs. Mac Catalyst vs. AppKit. There are compromises with each path.

    We are a small team, and maintaining so many different versions of our apps is difficult. On top of that, why invest so much time in Apple-only frameworks when Apple could upend our business with a new App Store tax or other disruptions?

    Going forward, the tentative plan is to abandon most of the current iOS codebase for Micro.blog, instead sharing it predominantly with Android using React Native. This will free up development time to keep making the Mac version even more Mac-like, sticking with AppKit.

    Mobile platforms like iOS and Android are much more similar to each other than either one is to the Mac. I love the Mac and don’t want to compromise the UI on macOS with a cross-platform framework. macOS also remains the only open Apple platform, so investing in it feels right.

    What about our other companion apps for Micro.blog? Sunlit and Wavelength will stay iOS-only using UIKit. Epilogue will move to React Native, simplifying the number of cross-platform frameworks we use to just one.

    In summary, here’s how I see our apps looking after this multi-year transition:

    • Micro.blog for the web: Ruby. Built for the web, for any platform.
    • Micro.blog for iOS: React Native.
    • Micro.blog for Android: React Native.
    • Micro.blog for macOS: AppKit.
    • Sunlit for iOS: UIKit.
    • Wavelength for iOS: UIKit.
    • Epilogue for iOS: React Native.
    • Epilogue for Android: React Native.

    The official apps for Micro.blog are a baseline. There should be a rich ecosystem of third-party apps that make other choices, going all-in on SwiftUI, Jetpack Compose, or whatever else helps developers build something new for Micro.blog.

    What makes the business side of Micro.blog work is that our goals as a company are aligned with our users’ goals. We make money when we provide features that make blogs better and the Micro.blog community experience better. Restructuring our development approach similarly aligns our priorities, moving just a little farther away from being dominated by Apple.

    → 3:50 PM, Feb 9
  • RSS
  • JSON Feed
  • Surprise me!